mirror of
https://github.com/Inside4ndroid/TMDB-Embed-API.git
synced 2026-10-22 17:45:11 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25cc59659d | ||
|
|
ab18e06a94 | ||
|
|
25acff24a7 | ||
|
|
0e9f6d92e4 | ||
|
|
3433c093bd | ||
|
|
e489a1b613 | ||
|
|
484ea4bb3f |
+3
-1
@@ -1 +1,3 @@
|
||||
/node_modules
|
||||
/node_modules
|
||||
restart.trigger
|
||||
.vscode/
|
||||
+67
-1
@@ -1,5 +1,71 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [1.0.4] - 2025-09-18
|
||||
|
||||
### Added
|
||||
- New provider: `moviesclub` with registry mapping (`moviesclub.js` → `getMoviesClubStreams`).
|
||||
|
||||
### Changed
|
||||
- Unified provider stream schema across all providers:
|
||||
```json
|
||||
{ "title": "…", "url": "…", "quality": "…", "provider": "…", "headers": { } }
|
||||
```
|
||||
- Provider integration docs: When adding a provider, also add its exported function to `providerFunctionMap` in `providers/registry.js`.
|
||||
- README Docker quick run snippet updated to: `docker run -it -p 8787:8787 inside4ndroid/tmdb-embed-api:latest`.
|
||||
|
||||
### Documentation
|
||||
- README: Updated Providers section, unified schema example, MoviesClub listed, registry mapping instructions, and badge version.
|
||||
|
||||
## [1.0.3] - 2025-09-17
|
||||
|
||||
### Fixed
|
||||
- Server now binds to `0.0.0.0` by default so Docker port publishing works correctly from the host. Added `BIND_HOST=0.0.0.0` in Dockerfile and compose.
|
||||
|
||||
### Notes
|
||||
- If you were seeing `ERR_CONNECTION_REFUSED` on `http://localhost:8787`, pull the latest image or rebuild, then re-run with `-p 8787:8787`.
|
||||
|
||||
## [1.0.2] - 2025-09-17
|
||||
|
||||
### Fixed
|
||||
- Standardized Docker port to `8787` everywhere (Dockerfile `EXPOSE`, compose `ports`, healthchecks, and README examples). Previous release notes mentioned 8787 but some environments weren't reset; this release ensures consistency.
|
||||
|
||||
### Documentation
|
||||
- Updated README version badge to 1.0.2 and verified Docker commands use `-p 8787:8787`.
|
||||
|
||||
## [1.0.1] - 2025-09-17
|
||||
|
||||
### Added
|
||||
- Admin UI: Restart Server button (below Logout) with a themed confirmation modal. The UI polls `/api/health` and auto-reloads when the server is back.
|
||||
- Sidebar divider under Logout/Restart for clarity.
|
||||
|
||||
### Changed
|
||||
- Exclude Codecs presets:
|
||||
- Introduced "None" (default) → `{ "excludeDV": false, "excludeHDR": false }`.
|
||||
- "All" → `{ "excludeDV": true, "excludeHDR": true }`.
|
||||
- Persist and render presets reliably after Save / Reload.
|
||||
- Minimum Quality: Clear All now resets to `"all"` and selects the All preset in the UI.
|
||||
- Clear All: Now fully resets TMDB keys, FebBox cookies, providers, and filters; added a themed confirmation modal with optional "Don't ask again" preference.
|
||||
- Live Config: Hide legacy `tmdbApiKey` (only show `tmdbApiKeys`).
|
||||
- Restart behavior:
|
||||
- Local dev: Nodemon watches `restart.trigger`; backend writes it before a clean exit to force a restart.
|
||||
- Docker: Compose uses `restart: unless-stopped`; Docker restarts the container after restart endpoint triggers exit.
|
||||
- Dockerfile: Ensure non-root `app` user owns `/app` for writing overrides and restart marker.
|
||||
- package.json scripts: Simplified to `start`, `start:dev`, and `lint`; both start scripts are nodemon-based and watch `restart.trigger`.
|
||||
|
||||
### Fixed
|
||||
- Provider matrix re-renders immediately after Clear All (no page refresh needed).
|
||||
- Handling of Exclude Codecs "ALL" previously not persisting correctly.
|
||||
|
||||
## [1.0.0] - 2025-09-16
|
||||
- Initial stable release.
|
||||
|
||||
[1.0.3]: https://github.com/Inside4ndroid/TMDB-Embed-API/compare/v1.0.2...v1.0.3
|
||||
[1.0.4]: https://github.com/Inside4ndroid/TMDB-Embed-API/compare/v1.0.3...v1.0.4
|
||||
[1.0.2]: https://github.com/Inside4ndroid/TMDB-Embed-API/compare/v1.0.1...v1.0.2
|
||||
[1.0.1]: https://github.com/Inside4ndroid/TMDB-Embed-API/compare/v1.0.0...v1.0.1
|
||||
|
||||
## [1.0.0] - 2025-09-16
|
||||
|
||||
### Added
|
||||
@@ -47,4 +113,4 @@ This 1.0.0 release consolidates modernization work: provider cleanup, configurat
|
||||
|
||||
---
|
||||
|
||||
[1.0.0]: https://github.com/Inside4ndroid/TMDB-Embed-API/releases/1.0.0
|
||||
[1.0.0]: https://github.com/Inside4ndroid/TMDB-Embed-API/releases/v1.0.0
|
||||
|
||||
@@ -22,6 +22,7 @@ ARG VERSION=dev
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production \
|
||||
API_PORT=8787 \
|
||||
BIND_HOST=0.0.0.0 \
|
||||
APP_VERSION=${VERSION}
|
||||
|
||||
# Create non-root user
|
||||
@@ -39,6 +40,8 @@ COPY --from=build /app/README.md ./
|
||||
# Expose port (documentational; runtime can override)
|
||||
EXPOSE 8787
|
||||
|
||||
# Ensure runtime user owns app directory for writes (overrides, restart marker)
|
||||
RUN chown -R app:app /app
|
||||
USER app
|
||||
|
||||
# Labels / metadata
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<img src="https://img.shields.io/badge/Node.js-18%2B-brightgreen?style=flat" />
|
||||
<img src="https://img.shields.io/badge/Status-Active-success?style=flat" />
|
||||
<img src="https://img.shields.io/badge/License-MIT-blue?style=flat" />
|
||||
<img src="https://img.shields.io/badge/Version-1.0.0-informational?style=flat" />
|
||||
<img src="https://img.shields.io/badge/Version-1.0.4-informational?style=flat" />
|
||||
<img src="https://img.shields.io/docker/pulls/inside4ndroid/tmdb-embed-api?label=Docker%20Pulls&style=flat" />
|
||||
</p>
|
||||
|
||||
@@ -34,14 +34,15 @@
|
||||
|
||||
## ✨ Features
|
||||
- **Multi‑TMDB Key Rotation** – Supply multiple API keys; one is chosen randomly per request.
|
||||
- **Provider Aggregation** – Pluggable providers (Showbox, 4khdhub, MoviesMod, MP4Hydra, VidZee, Vixsrc, Xprime) with per‑provider enable toggles + default selection.
|
||||
- **Provider Aggregation** – Pluggable providers (Showbox, 4khdhub, MoviesMod, MP4Hydra, VidZee, Vixsrc, Xprime, MoviesClub) with per‑provider enable toggles + default selection.
|
||||
- **🔥 Plugin System** – Drop new provider files in `providers/` and add its exported function to the registry map (`providers/registry.js` → `providerFunctionMap`).
|
||||
- **Dynamic Filtering** – Minimum quality presets, custom JSON quality map, codec exclusion rules (presets + JSON).
|
||||
- **Runtime Overrides UI** – Fully interactive web admin at `/` (login protected) writing to `utils/user-config.json`.
|
||||
- **Session Auth + Rate Limiting** – Login system with brute‑force lockouts, logout, password change.
|
||||
- **Status & Health Panel** – Live metrics, provider status, endpoint list, functional provider checks.
|
||||
- **Config Propagation** – Overrides mirrored to `process.env` for legacy compatibility (no `.env` required after first save).
|
||||
- **Back‑Navigation Safe** – Cache-control + visibility/session revalidation.
|
||||
- **Extensible** – Simple provider registry pattern.
|
||||
- **Extensible** – Simple drop-in provider plugin system.
|
||||
|
||||
---
|
||||
|
||||
@@ -53,11 +54,11 @@ npm install
|
||||
# 2. (Optional) Copy example env if you want an initial TMDB key
|
||||
cp .env.example .env # then edit TMDB_API_KEY=
|
||||
|
||||
# 3. Start API (production style)
|
||||
node apiServer.js
|
||||
# 3. Start API with automatic restarts (recommended for local dev)
|
||||
npm start
|
||||
|
||||
# Or with automatic restarts (if you add a dev script)
|
||||
# npm run dev:api
|
||||
# Or production-style single run
|
||||
# node apiServer.js
|
||||
|
||||
# 4. Open the Admin UI (login page) in browser
|
||||
http://localhost:8787/
|
||||
@@ -79,6 +80,11 @@ docker run --name tmdb-embed-api -p 8787:8787 \
|
||||
inside4ndroid/tmdb-embed-api:latest
|
||||
```
|
||||
|
||||
Or the minimal quick-test run:
|
||||
```bash
|
||||
docker run -it -p 8787:8787 inside4ndroid/tmdb-embed-api:latest
|
||||
```
|
||||
|
||||
Persist overrides (Windows PowerShell example) by mounting a local file:
|
||||
```powershell
|
||||
New-Item -ItemType File -Path .\utils\user-config.json -Force | Out-Null
|
||||
@@ -142,6 +148,11 @@ If both `TMDB_API_KEY` and `TMDB_API_KEYS` are provided, rotation uses the array
|
||||
```bash
|
||||
docker compose pull # if using an external registry (future)
|
||||
docker compose up -d --build
|
||||
|
||||
### Restart from Admin UI
|
||||
The Admin panel includes a Restart Server control.
|
||||
- Local (nodemon): the backend writes a `restart.trigger` file and exits; nodemon detects the change and restarts automatically.
|
||||
- Docker Compose: the container exits and is restarted by `restart: unless-stopped`.
|
||||
```
|
||||
|
||||
### Healthcheck
|
||||
@@ -188,7 +199,7 @@ Saving in the UI writes only changed keys. Setting a field to empty removes the
|
||||
| Quality / Filters | Min quality presets & codec exclusion JSON |
|
||||
| Keys | Add/remove TMDB API keys (rotated randomly) |
|
||||
| FebBox / PStream | Manage FebBox cookies powering Showbox provider |
|
||||
| Advanced | Provider toggles, cache & validation flags, proxy paths |
|
||||
| Advanced | Provider toggles, cache & validation flags |
|
||||
| Server Status | Live metrics, provider functional checks |
|
||||
| Live Config | View merged + override JSON snapshots |
|
||||
|
||||
@@ -197,22 +208,64 @@ Session is revalidated on visibility and back/forward navigation to prevent stal
|
||||
---
|
||||
|
||||
## 🔌 Providers
|
||||
Current built‑in providers:
|
||||
- `showbox`
|
||||
- `4khdhub`
|
||||
- `moviesmod`
|
||||
- `mp4hydra`
|
||||
- `vidzee`
|
||||
- `vixsrc`
|
||||
- `xprime`
|
||||
The API supports a plugin system. Drop a new provider file in the `providers/` folder and register its exported function in `providers/registry.js` under `providerFunctionMap`.
|
||||
|
||||
You can enable/disable and mark defaults via the Provider Matrix in the UI. If **no defaults** are selected, all enabled providers participate in aggregation.
|
||||
### Current Built-in Providers
|
||||
- `showbox` - Showbox/PStream integration
|
||||
- `4khdhub` - 4KHDHub streams
|
||||
- `moviesmod` - MoviesMod streams
|
||||
- `mp4hydra` - MP4Hydra streams
|
||||
- `vidzee` - VidZee streams
|
||||
- `vixsrc` - Vixsrc streams
|
||||
- `xprime` - Xprime streams
|
||||
- `uhdmovies` - UHD Movies streams
|
||||
- `moviesclub` - MoviesClub streams
|
||||
|
||||
### Adding a New Provider (Outline)
|
||||
1. Create `providers/myprovider.js` exporting an async fetch function (see existing files for patterns).
|
||||
2. Lazy‑load it in `providers/registry.js` similar to others.
|
||||
3. Add a config flag (`enableMyproviderProvider`) if you want toggle support.
|
||||
4. Update the provider matrix arrays in `public/index.js`.
|
||||
### Adding a New Provider
|
||||
1. **Create** `providers/yourprovider.js` with your stream fetching logic
|
||||
2. **Export** a function like `getYourproviderStreams(tmdbId, mediaType, season, episode)`
|
||||
3. **Register** it in `providers/registry.js` → `providerFunctionMap`:
|
||||
```js
|
||||
// providers/registry.js
|
||||
const providerFunctionMap = {
|
||||
'Showbox.js': 'getStreamsFromTmdbId',
|
||||
'4khdhub.js': 'get4KHDHubStreams',
|
||||
'moviesmod.js': 'getMoviesModStreams',
|
||||
'MP4Hydra.js': 'getMP4HydraStreams',
|
||||
'VidZee.js': 'getVidZeeStreams',
|
||||
'vixsrc.js': 'getVixsrcStreams',
|
||||
'xprime.js': 'getXprimeStreams',
|
||||
'uhdmovies.js': 'getUHDMoviesStreams',
|
||||
'moviesclub.js': 'getMoviesClubStreams',
|
||||
'yourprovider.js': 'getYourproviderStreams'
|
||||
};
|
||||
```
|
||||
4. The provider will appear in the admin UI with an enable/disable toggle.
|
||||
|
||||
**Example Provider (Unified Output):**
|
||||
```javascript
|
||||
async function getYourproviderStreams(tmdbId, mediaType, season, episode) {
|
||||
// Your scraping/API logic here
|
||||
return [{
|
||||
title: "Fight Club - 1080p [YourProvider #1]",
|
||||
url: "https://stream.url/video.mp4",
|
||||
quality: "1080p",
|
||||
provider: "yourprovider",
|
||||
headers: { "User-Agent": "Mozilla/5.0" }
|
||||
}];
|
||||
}
|
||||
|
||||
module.exports = { getYourproviderStreams };
|
||||
```
|
||||
|
||||
> **⚠️ Important**: All providers must return streams in the unified JSON format to ensure compatibility with filtering and aggregation.
|
||||
|
||||
The system automatically:
|
||||
- ✅ Detects new provider files
|
||||
- ✅ Adds enable/disable toggles in the admin UI
|
||||
- ✅ Includes them in stream aggregation
|
||||
- ✅ Applies filtering and quality controls
|
||||
- ✅ No core file edits required!
|
||||
|
||||
---
|
||||
|
||||
@@ -233,18 +286,14 @@ Aggregate endpoint auto-resolves IMDb when needed and merges provider output bef
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Stream Object Shape (Typical)
|
||||
## 🧪 Stream Object Schema (Unified)
|
||||
```json
|
||||
{
|
||||
"title": "Example Title",
|
||||
"url": "https://...",
|
||||
"title": "Fight Club - 1080p [MP4Hydra #2]",
|
||||
"url": "https://stream.url/video.mp4",
|
||||
"quality": "1080p",
|
||||
"provider": "showbox",
|
||||
"size": null,
|
||||
"languages": ["en"],
|
||||
"subtitles": [],
|
||||
"codecs": ["H264"],
|
||||
"headers": { "Referer": "..." }
|
||||
"provider": "mp4hydra",
|
||||
"headers": { "User-Agent": "Mozilla/5.0" }
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+32
-3
@@ -66,8 +66,11 @@ function recordLoginSuccess(ip){
|
||||
loginAttempts.delete(ip);
|
||||
}
|
||||
|
||||
// Guard against premature process.exit from imported legacy modules
|
||||
// Guard against premature process.exit from imported legacy modules, but allow controlled restarts
|
||||
const realProcessExit = process.exit.bind(process);
|
||||
let allowControlledExit = false;
|
||||
process.exit = function(code){
|
||||
if (allowControlledExit) return realProcessExit(code);
|
||||
console.warn('[diagnostic] Intercepted process.exit with code', code, new Error('exit trace').stack);
|
||||
// keep process alive for debugging
|
||||
};
|
||||
@@ -198,6 +201,28 @@ app.post('/api/config', (req,res) => {
|
||||
res.json({ success: ok, merged: config });
|
||||
});
|
||||
|
||||
// Restart endpoint (requires auth via session cookie on /config.html UI)
|
||||
app.post('/api/restart', (req,res) => {
|
||||
const sess = getSession(req);
|
||||
if(!sess) return res.status(401).json({ success:false, error:'UNAUTHORIZED' });
|
||||
res.json({ success:true, message:'RESTARTING' });
|
||||
// Give the response a moment to flush
|
||||
setTimeout(()=>{
|
||||
try {
|
||||
const fs = require('fs');
|
||||
const restartMarker = require('path').join(process.cwd(), 'restart.trigger');
|
||||
fs.writeFileSync(restartMarker, String(Date.now()));
|
||||
console.warn('[control] wrote restart.trigger to notify nodemon');
|
||||
} catch (e) {
|
||||
console.warn('[control] failed to write restart marker:', e.message);
|
||||
}
|
||||
console.warn('[control] restarting process by exit(0)');
|
||||
// Let nodemon detect the file change and restart the app
|
||||
allowControlledExit = true;
|
||||
realProcessExit(0);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// --- Basic informational endpoints ---
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ ok: true, service: 'tmdb-embed-api', time: new Date().toISOString() });
|
||||
@@ -349,8 +374,12 @@ app.get('/api/streams/:provider/:type/:tmdbId', async (req,res) => {
|
||||
});
|
||||
|
||||
const PORT = config.port;
|
||||
const server = app.listen(PORT, () => {
|
||||
console.log(`TMDB Embed REST API listening on http://localhost:${PORT}`);
|
||||
const HOST = process.env.BIND_HOST || '0.0.0.0';
|
||||
const server = app.listen(PORT, HOST, () => {
|
||||
console.log(`TMDB Embed REST API listening on http://${HOST}:${PORT}`);
|
||||
if (HOST !== 'localhost') {
|
||||
console.log(`Local access (if running on your machine): http://localhost:${PORT}`);
|
||||
}
|
||||
console.log('Endpoints:');
|
||||
console.log(' GET /api/health');
|
||||
console.log(' GET /api/metrics');
|
||||
|
||||
@@ -9,6 +9,7 @@ services:
|
||||
# Alternative: supply JSON array
|
||||
# - TMDB_API_KEYS=["key1","key2"]
|
||||
- API_PORT=8787
|
||||
- BIND_HOST=0.0.0.0
|
||||
# FebBox cookies (comma separated) if you want Showbox immediately functional
|
||||
- FEBBOX_COOKIES=${FEBBOX_COOKIES:-}
|
||||
ports:
|
||||
@@ -16,6 +17,8 @@ services:
|
||||
volumes:
|
||||
# Persist runtime overrides (created by the admin UI)
|
||||
- user_config_data:/app/utils/user-config.json
|
||||
# Allow restart trigger file (optional; stored in container FS)
|
||||
# - restart_marker:/app/restart.trigger
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO- http://localhost:8787/api/health || exit 1"]
|
||||
@@ -25,3 +28,4 @@ services:
|
||||
retries: 3
|
||||
volumes:
|
||||
user_config_data:
|
||||
# restart_marker:
|
||||
|
||||
+3
-4
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "tmdb-embed-api",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.4",
|
||||
"description": "Standalone streaming embed source aggregation API (TMDB ID in, streams out)",
|
||||
"main": "apiServer.js",
|
||||
"scripts": {
|
||||
"start": "node apiServer.js",
|
||||
"start:api": "node --watch apiServer.js",
|
||||
"dev:api": "nodemon --ignore providers/.cache --ignore **/tmdb-embed-showbox-cache apiServer.js",
|
||||
"start": "nodemon --watch restart.trigger apiServer.js",
|
||||
"start:dev": "nodemon --watch restart.trigger --ignore providers/.cache --ignore **/tmdb-embed-showbox-cache apiServer.js",
|
||||
"lint": "eslint . --ext .js"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
"https://hubcloud.one/drive/eeltgk7tefggdkw",
|
||||
"https://hubdrive.space/file/2839373447",
|
||||
"https://hubcloud.one/drive/zm1uzufz8wh1zmv",
|
||||
"https://hubdrive.space/file/6441436585",
|
||||
"https://hubcloud.one/drive/dm1sbdubufkkdsv",
|
||||
"https://hubdrive.space/file/3875373190",
|
||||
"https://hubcloud.one/drive/wev1xt17dypotb1",
|
||||
"https://hubdrive.space/file/4216265302",
|
||||
"https://hubcloud.one/drive/mrhza1jqmajrjko",
|
||||
"https://hubdrive.space/file/2736804525"
|
||||
]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
"https://hubcloud.one/drive/2rc17eosl11jalt",
|
||||
"https://hubdrive.space/file/30320495116",
|
||||
"https://hubcloud.one/drive/lb82vul8vwzw8lz",
|
||||
"https://hubdrive.space/file/24320017865",
|
||||
"https://hubcloud.one/drive/ls4j4srjsh228ks",
|
||||
"https://hubdrive.space/file/8303893568",
|
||||
"https://hubcloud.one/drive/cds5rdggp8pwxds",
|
||||
"https://hubdrive.space/file/8032803119",
|
||||
"https://hubcloud.one/drive/gb655ffb5nmzg3g",
|
||||
"https://hubdrive.space/file/2836399263"
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
"https://hubdrive.space/file/5855904942",
|
||||
"https://hubdrive.space/file/5855904942",
|
||||
"https://hubdrive.space/file/27692865336",
|
||||
"https://hubdrive.space/file/27692865336",
|
||||
"https://hubdrive.space/file/6521988586",
|
||||
"https://hubdrive.space/file/6521988586",
|
||||
"https://hubdrive.space/file/9974112091",
|
||||
"https://hubdrive.space/file/9974112091",
|
||||
"https://hubdrive.space/file/8806133211",
|
||||
"https://hubdrive.space/file/8806133211",
|
||||
"https://hubdrive.space/file/6167785635",
|
||||
"https://hubdrive.space/file/6167785635",
|
||||
"https://hubdrive.space/file/10054935010",
|
||||
"https://hubdrive.space/file/10054935010",
|
||||
"https://hubdrive.space/file/4750964205",
|
||||
"https://hubdrive.space/file/4750964205"
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"data": null
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"quality": "720p",
|
||||
"driveseedRedirectLinks": [
|
||||
{
|
||||
"server": "✅ Fast Server (G-Drive)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEtlZnl5UDZCUGpyd1B0aW1WK01GOERRK0V6MDJ5MFNGbGh1Q1h4Y3VYckNXSDZ2MlIxMDVYMnRyeDMydFBNdWNEajRSbDZXMWlzRjNMaVhMdnhDZkVqK1lkeEFrTFVKcnpBemFRSUJ4NStWTE51aHJIM1pOSUlMK3NxMDMwcUVpZXlPR0dPL2tzQmlWUDFmNkhGR0RSbg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=Z1JOMXJJbTVlVFFIdXcvQ2xRMFZ5ZEtGekMrZk1ISkY1b3d4MUhray8yM2xPVHRGK00zTWtmekhYcjJRQXBLYw=="
|
||||
},
|
||||
{
|
||||
"server": "🚀 Google Drive (Server 2)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEpaWllxaHVJQ3JtNmZUb1cyMy92NkpPRWdIekdtNjNwTDJRTEh0Z3Mva1JYTUthaStDUVp3UzlRa0hqVVlyQjZWdExtZG1wTitoMmRzVXpyWHZRZ0tPaFpVR3lQcmFzUVBSb2hkdE1wWlB6d0s5ZGsyb2UwOCtGRWZKQS9wKzhaZVRrTjBCendhZVV3YksrOGNXZkdJdg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=UkZYTyswOUlYWnRkd3c4UFg5TWEyNkxMRXdFakZNL05CMjYxcElpOGxOZWxJUEVMQXo4b2V6alh0RWJTemJEcg=="
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"quality": "1080p",
|
||||
"driveseedRedirectLinks": [
|
||||
{
|
||||
"server": "✅ Fast Server (G-Drive)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEtYaWxrRmU5aEVETnNSaE0veXJXN1VnYXJjZTVyQ2hVNUtzbk85MDI0OC9Edm9EUWVpWHNMYU0rRXp0ZGpUWTdFZG5VQzR6T0x3UmgvUEY5R1lFc2lrRFkxK3B3N1A1V0htN3h3eDJyaTY5REJSK1RjVm00MkNnRk9XQW12eCtRWE5seWxFZC9uelNXWUNOSXpBbllkaw==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=QXhGREliQjNpZitEWFJZY01qdXc4cmt4SnRMNGlGcit0eGNkbDNySHUybnVWZjIycElLZ0VTNGdMV3A1S1lKVw=="
|
||||
},
|
||||
{
|
||||
"server": "🚀 Google Drive (Server 2)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZElqd1lDTmJPOWRrK2tMc0ZTMENiQTFtRkZkUVBZVjArQ1d4NFhvdzhNcnhsalBHbm82dnVTY3ppS1Jla3RWTHVxMmxuemhzWUdEczdnMElYS3lEN1Y4WEp2bXdxb3FndWFJKzVHRWE1Y1ljR3dkamZQR0U4czV3Y0ZKcUVvSFVRVVdIQ1RITnBtTGIvelVIeXljQWd5Sg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=ZXV0eEdJYlhJcFhad0FSeDQxWFRGajZGZ3NDZG9BQTQ1c0JvWVUyMnFBMjJIam5zdUE4UnE2dlIwRjVVYVBERg=="
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"quality": "720p",
|
||||
"driveseedRedirectLinks": [
|
||||
{
|
||||
"server": "✅ Fast Server (G-Drive)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZExIOWo1MEtyUTN4L3dUeW8wbzc3VUFXODNBRFZSaFBpUHNwdTZ4aEF2YVJUdVlodzI0Q0pFU1hnQ0ZaUUFHOVNsaEsrWFI0RkY0SFFhYTEyWjFBdFNwNGlHczdNaXpEOS9qeXdVT2dsMU10anpscjVnN21IdmpCcVoyY2x4djJzVzZXb3ZkM0FxM2Q1SXdvR0o5Q3cvNg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=K1U1RnhlTVdFc25JakJLVFZlTzlaN0w3V2VFRnVlelBLQWRaOVVpVXNBZWFwWksvREw0QmxWQjFCS0pDYWlXOQ=="
|
||||
},
|
||||
{
|
||||
"server": "🚀 Google Drive (Server 2)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZExqUmh2aXFIeEJDWjQwZkpVVUpZWWZUQzZzeVh4L3VMblgyR1F4eGJjSDc1OVBRQ2VxeHZyb21acHh5SzNvRkhneXBPTmdhSGgvTEh4QnNKMlJoVUJSUnpvempYdExaZUZkcWQ5VnN2TXFqRzJwYXRvSDF4bk1iTXZ1UlZ0R0JtQjhXYmNEQVE0emhmVlFOR1NLODh1eA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=aExxTjRHZk4wY1ZjNDJ2VHhzcjdrcVBweHBJbWhGNitYN1ZlUGR4Z0gxalR1T2orZFBFdTRLMnRuUndiVE5IQg=="
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"quality": "1080p",
|
||||
"driveseedRedirectLinks": [
|
||||
{
|
||||
"server": "✅ Fast Server (G-Drive)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEtPNXdWSEEvK1RsbzRDdDJlY0xTd0xlY1FDbHJhK0RjdUlWKzlmZkl6dG1IRWFkTStReXlSUGtXNHhGWG5jZEJDc2ZnUytzK281d3E4VXJVcFdNN0lxZVVjK2F4Y2hhVDAvL2pJRXVHNGMybk5tQW1uSkh5d2p5VlFqTDdaWGdLcUwzMGF6aFNibjkwZTVHd2l5eXRPTg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=Y08zbk11amZCd3NacmxONVJZYlNNN3FIaklVU0lSSXp6VWhVUTkxVzlpWEJWNVFSOXQ5MTFUWmQvUFNCSzQ0WA=="
|
||||
},
|
||||
{
|
||||
"server": "🚀 Google Drive (Server 2)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEozOWlUeUxnd2JuT1l5WHNvQlNVUWpNeGNzTEplSExmN2R4ZDNQVm55Y243VHFicHBVREVZUHQraVZJUmxLeVFYVFArb0Y0ejc2MEUzRE1sL2tsRStKdUNwMW1GSEdkZ0ZrR09Bdzl0ZzN5M3ErU1RCTGdFV1F5UVNiOEtqRVlRcmNNckFZQ0xSbW5vbTRMdW1TOVg5UQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=Vk5abDBBaEhiaE9rNE8xTHNwR1BZQVZZUTJ0Qjc2bzFzdHVhVUV5VWdPVnNHOWl0ek40S1FxZ1U3YmUwWTdVNg=="
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"quality": "Season 1 {Hindi-English} 720p 10Bit Esubs [340MB] - Episode Links",
|
||||
"driveseedRedirectLinks": [
|
||||
{
|
||||
"server": "Episode 1",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEw0Y0tZeDhyUmtkbTFtM0tEay90aGM0TmVJNFdxTGk2MVpORURBQUNNaHRKOGJKZlRkdXNnZzMwb1JPaXRrN0VoV1hReFZyNzNUWFBsQ2R6aGkzKy80VXloNGtQZmllUkhMbGdmcXd0Y01LbVRPd1Foc283eFhQL1VKR3pocGZWZEp0bUJTM1VYRzNGTzYxU0VSd0hwbg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=UnRjNmRSQ3pLV29ONnRUS0kvYmY3RjV6OTRGUCtpbzRLWE81ejgvM0hrYUMzMjM0cEpnSEh3OGVUTGxXY3BrRQ=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 2",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZElRaTlRd1loWlRLNFhDL3UxeEZxRFl5VW93a3h4eTVyYWNRUFJMYnJoTnFzbHZpQ2hFejZEc1N3RHJYd05UODlnN29MZURUN2tRTTdUQVg2cEZGcVBuUHJLNEpPaVpzREd6d0ZyNzBHRzFrc3F0aktLYW1zaEhTM3FaSnRBTjF6b2x0aU5NU2FMdU40aTA0dkxMdzVQTg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=bkd6amRlRWE0UlhxV0dqRlo4b1B3TmVCZklXTWRMWnNRbXlDYWdFTnJYWHFIQ2lqOWl5K0pCT2pMaWdXQzdpbw=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 3",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEtLYmtEYjdWVllPYjZLMm1TMXNZaFl2WXdKUk1kbkR5d0Z3Ylo3TTRNSEo4VWxrZnFQVEMvTzduTDhjNG1ITm4zTVdHc3pzdlhMVFVOOHpOSnNWLzMyOHg0SDdFNi9nSThJaFczRkFMV21NVWZ6Nk0ybUNvejQxanF2OWZZUHhBREY2cWNVcCtyMm1XeDBBdFpYWFFlRQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=azkzOHJGMHR2T2YzRUhwWU8raUJuam9MVzZ2UWNXdUNkVVNMdjgrN3FBM1JEVkhJZFlZajcrd1JERENQQ3pCRA=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 4",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEw5L1JZVWZxK09QMWtxbEFWU2ppdUx2SmhKQVFrK0FNNEY0NkJsUDJjZlprQTI3eTlBRW5kaEpQSlN1Vks5RzRUMFBVL0FvQ1hJa2J2R2JIV2ovYUNNVkdZYm9hQjNPY2hLTVZyZml4SUQ0YmROUDFOZm5HbVc1SWpvaXRPZHM0d1ZFMDFUOFBPR0VGRW5qdy9RbTNDSw==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=QVdhWE5rVkhmV3B3dHJKTUQzNEd2VDM3cVJNMW9pN05qQUNDTnJ3ZGR2WmVKR2FVS25heUlob0ZOSnlUUS9TUA=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 5",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZExsVjhoaC83WDZCSDZvUlo2bEtOOTNmUmNzUjlmaHlKS01GK2VmcHVTOGQ5RUlEOFhLTnBtdk9LTHRmSTJjU3NLZHdyZEg0a0Z1ZjFCUGxHbzJ2WGo5TDhmNnhyUTR4WkR4MG9hc3EyOTZadkYzN00xYXRqTGt5YnQrZGFVTUdkWktxV3JJcDdubGpoSVN0SkZzdWZ3dQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=Qy9GNFN3U2loNWsyRFVGUUh3RnpmbSs1UURJTVllOEJzNDdwOVhHcUxUci9rZWdOS1JIS0xmVGw5L3QxWGZzSg=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 6",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEtGTjA3WlR0MUwvODZqckhBQWFOVUNCUWtQaUVZbzFxTmx6UWpOUFJxMnVCcitiWEtpdGtiSEJSNW5iUG5XSkxwWFZ6a1g2b3BkbFJmN1JYblIweW1xY2R6ZjEvSW9xRU5xZnZTQlRpS3lnU0d1NThhdTZhMFAvUGdYQzl0TWprSkRLa1RwTzVJd1N0N1p0NkhLdkp0cQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=WjhBd0J6a09PUWVYeDlaRG5yNU5qcE5KM09ITks2d2NqTkM1UGxtR0c2T2VBTEhnanByRVdBanN0MzJyNDk4UQ=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 7",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZExRVlYyVkpzdWxBWUo3bzBzcWs5d3R3dHNXOWxqUHVlWFdkTlgxSGhaUS9FU1E4NXdCS3laSVlSZUlQTDBFWkxnMGkxOUkzdklmellSR2RyTk9uSXpTWk0vWC9jYkVHdFlQOThjZUs5ckNTNGRraGlFYnRmQ3FhcDdIdEF4ZFdEdjQ0MHJPTytZVUFLRTZVdkhMb2ZUUQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=ZjJqSGRoWUtpMW56VlhwTUpzWFVWSTdMazRDSnZJQlQzb0p6dXdTbU1xc2ZrK0FxMzNHSUQzS0hIMDhuS1NUOQ=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 8",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEkzdVZ2UWpRV01CWWJhczc1VUZiM1A0aWlGSHNPbzFnNmNudkdnUXU2QVVUUTBDUGJjTEJIQ2tMSHAzeExiR2dEbXVYaWNZdWtuUFVFUklFbWFLeWNNb2VDRXJ3aVdubmN0UE14bG5NbTV5TkF3VWtWS2RFU2s3WkszRVJpVEVuY055VjNtNWJLVzhSUHV4U1BDMVJnVQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=Tng3MmRaUWFERkFaR3ZnR0ZaSzJDaklkMVZudWFFSkE2TGxEREpDYVFUV2J2M1FpNjc1ZHVQajJtT21QZ2t1TA=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 9",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEp6eS9jcnJib1hHeGRwY0RYbHJoMGhSMTlDNUZvSSt0RUlvK2tjRloraWt4Mm5MYUl1MktxbHU5Ry9pS0FuL21TYkphTnZEaXJGdkx6RG9YR3VwaVVpT2g3UkFhUTZjS1Z1eGZaLzhiWjBtUmlYeWFQL1JWbUMwQmdBRnRkYkRhanhqSjJ0aXNJUTRGQy9LRmk4a3V6WA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=bDZiZXNON0lNTjRCS0tDbDRlb1dRRTk5NCt1V2ZUV1piNGZ3eExZQVFPRVYweEVkWHMzOEJUUGxlOXRLa0RpbA=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 10",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEpFZ1Q4elA2S2x2aTZzQ2tvNXBiMVBHclRJbFMraTBPRHVjQUt0SU8raEg2RWlpZ25leFhlQnNialIyQUJWWWlQMEtWUFJsdTM0Z2NmNGJWOGNLcXRDSzhKNlNUa01WK1grTTRBaGdVT1I2VTE3UEZ6ZDVLVjFkVjdVZFZQU1AxaUtqdVRnNnhNT2tZL1JMMExTcEY0Kw==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=OWRIYzFabCtGM1gzQ3RPRlJreFo5Ykt4MHVKeFYwTW5SRndaQ3NqOEg0UzRiQ2FDZlJ0d0ZEcDA3cTQxSHZzNA=="
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"quality": "Season 1 {Hindi-English} 720p x264 Esubs [500MB] - Episode Links",
|
||||
"driveseedRedirectLinks": [
|
||||
{
|
||||
"server": "Episode 1",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZElNMHVBSFVUOGhqOUdXMGtDeDRzeVMrSHlPblJSSFlEMi9ZY2R3OFNXNGJRRk1MelNBRTR6NGlPU2ozVXd4cFFRaFM1MlV4QXVlTWhTK2F2akZNaTBoYmc2ZFRoRmYvcnJDdUJZdDY2RGlFZFM5ZzdRNmF5WUZRU0xVYjNTS1dIUWZ1S1JXcS8rQXBMTGNnVTRmSE9MMg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=c2RhVDROL3JOZG5GV3RoWndtbi9RMHFnN1YrSDF6eUtuSlA4WWR5ZXdOV0pIaGcvNC9BTWN4cURMSTlRRzR4WA=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 2",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZElzOHFpOW9YeGlEenVtQkpZUjhkaGFobmhVYnZRLzJDRTQ5SHNBRlowTis1QnpTOXFub1E3ZHE5cGpoUFhDVU1hbHNCbms5YXprWTh0N05UR09vOUN3TWI2WXFGUGh3SkdGUUFzSURPZlFMU3lvbk9LRW91T0xKZ1U5SGMyOUxsSFkySmlyZzBSYVBkbHBiS2dsblpMLw==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=WjJOc2dvVG8yTTlocmRzTXdEZ3NqWTNQa1hOd1ZnRFU1RnV5aWRpR3djaXFZK1IwVXYzYWxLa3lBUDRpSmkrQQ=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 3",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEp3RHNXZUxnK3l2VVVoL2hpLzdBckpCRTF2b1k2ZldidEhJMlpGc213Q0dUb3BIUlMwSW9ibmFtalBKSk9PQXJSRWZ3L3E3Yk1QdVA3NGloN21KVWo2L3hIaWV3QStmWGk3c0tWMS9neVRTU2swalV1bHhFM0ROeVFudTR4T00wSWVPNmpjR0dQK0U5NFl3SWNGRHZHRA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=YXN0eGVCaVRhL0hndU1JVTFqa3NRQlR0UE9YdEdEY1ZMOGxzRG8wbjQybldkWFc2MkR5MjR3WE9XS3QzZ2tZUA=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 4",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEtlSkVSTnJrbWVpZU9GK0lYTXVwV0xBMEtPaU9FTFNwaFJVNlBYWG4zMzYxazRqSkV2c2I2Q2FaSnBWVXNEZFRjMEg5bnVubEFDbnk3VUhxVXh6Sk9lWWhWejFvWmlUeXhrNjBXZ24vcU5pNVdMZ0JvNDZvUHZMS0hid3JYOVJYSENzem9BVUI0dHdCc2RpVjZUTXB1Rg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=Q3IydEdlS0NXbUdkeSttRXdqWk9aZFZGcFVpME9wQXVFMTRKTGpKci9qR2lIKzVOSmlzNHV2VUF1NFF1ekJKZA=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 5",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEtZMi8rMURQS1IydSs2UzRmZzBxbTV6cUR5NGxXN0pxVzFzZHhteTN4dG92MUlpZlU0dFBOTTgyRGRiSS9wZStzdmR4SngyVHNkV3kwZkVuS1phTDNBNzZJR2VRQWJ4NmM5MkxISHdvRkZXd3FwcVF6NjhJY2RwR0FRMzlsb2c4WkFaN1BLcUFKcldwVFVnNmpvVDF1cw==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=OWhTdjhaaS9uU2hSSzJTc2V1c0lGQmtFZHZ6eHRjKzFLQmhNNmlUSFJtVEc5dGVEdEFYQkF2aE1weFQrNTU5Rw=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 6",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZExFVkQ1Wm5Uc3pRcElVTW9rUE5IVXNHU3Ura0dXRVUzMHNrOWYreFU1WGRxd2wxd1RYYnJ5VnJadjRlM2tPcFVYdmE2dEVkZTRpU1h2TGVBVWhJQ1ZHaDZ4WnNDbVBFNVliWVNwQytPeGF5OEZLWndaZFNTbm1ORFVhZzY1NjhpZitIZ2NLZmYweVFwUnFGTyt4dkFaSA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=TTlaUm9vQXRjMVg2SXprZlFISDRrVDBpdzNwTUxyeG5RekVoWUp3Y3FGK214SFdTVTdkTzJBMDFHMU8xQ1VXdw=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 7",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEplNERKdG0wYk1xSzRGdVY2cFNQYmN1RTJCTU5BMXUrdFcvN3NsNG9ieWtUMjZaU2FDMytPZWp5V0lkeTBzZkhiZVhaRitwOVB5RWl2R3ZDT3BNTGNBOHFLSzZSN2c0ZmkrVWpQZlhVTWlncXBWVHE5MlpFMmI2a3BKSWhTVVdYeHYzRzJpdllXM0tHU2xRQ2tkb2VqWA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=SFNGTGpTZE1wTU1DSUwrK3FiOStyU2pzV1V2MWY5NGNyVmVnQ1FRZWRmSGxuQUFVSElGNGVXbU9NNHdTR3hRUw=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 8",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEs2SHp3TDFJcmxUQjBUNGlNYUorck5VMGdqbFlhZFhVRHk3YlRYdlRaVXJWZWtMenEybW8xWU1oNjgrNmJZNXp1cUVDd1BTVjBkbDJKZzh5bzgzamVITGF5Y0szamJwbW9paVYvSTA4OVpWVnV1V2dTZHpoVFJCbWg3R3UrUnd5VWx6VXhOYk95SEE1MXhPMHBnT2xVcQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=SmFzM2NEaTBkSjBBNW1MQkJEYlArdUJSUldGYWhxUlJoL1hQOVNPNUR2NnkycTl2aXhSN3FrMGhDcVJZTW4vVg=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 9",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEl3TVBneUl4Nnc4MDIvQUFCSWk5R1RzSklPS2hjMFZXVncyZGFMYUlZNEFZQzhTNkxFVVhzNDFKREVkQ01TaGJGZWlMNEhZY21vMUVNN0U5cVpicXpaV0g3RG1rUi93clhWcmlubWgvWlBLRGJQdUpOOUlYUWhXMkFubU9OMjRBVk9lMnp2YUtBQlNobDRNWVdBOTdmRQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=djU0OCtyWkdrMC9vRmNnbHdGektUVG5PdUlEaXNKUG9jL0JrV0t3ZzZnUklKUWQ2M3pkYXAxOS9oaU9tYkpmRQ=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 10",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZExySXZEUkJUaktxSWlWd3BXOHp1d0tvMlZGekxHa2ZCK3lpYkpzTVVEc2FKcTZjbXl6L2ljaGlCNThSRzVFUEZnUmg5ejd0cm1rZTh4QkRSVmNJd2hVS0wwRW9vYmJicXNVamZRTldDU0V0VGZBTjhmUzhVUm85TnNhbTcrQTRWalVBdUpWbkIwSnhmOHRWTzl4ZVN1bA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=aEZoSFlxZHRiK0F0OWtCSmxDOVRZdFE1VnlwaUZnOWozYUg2WDNNd0lzcnJ4bjNzVlY5akZ6TmI2NmNNNURrNw=="
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"quality": "Season 1 {Hindi-English} 1080p 10Bit Esubs [970MB] - Episode Links",
|
||||
"driveseedRedirectLinks": [
|
||||
{
|
||||
"server": "Episode 1",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEptWXBMMVZiYzVaZFYwYXp5emZBUjlkcmp5eG9PT1hYQjR2czJ1ME5MVmRyK2JtT0ZXci9uY1NVVEIvTFZ0WjVtUTR3ZzRqSFgwS3hhaWhseHpYWVFzOXpENS9ySlJ3U3AwbkRQdGR5cTVML1p0dzRVcys1V2oxUllZNU1CUXgxN3QxeUgvN01PYjNTN0JncE1nMmhxTA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=VlFVOVp3RThlc1phanBHdURQdm9HVVo2ZytmT1JqVGxkR0lqUS9ZUXBaLzE3RlhaL2JNbkxQcVV4SkJvNWY2dQ=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 2",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZExrc3ZYTXpxNWJoRFJFSWVjTFFTWXB4TWdaTUlhbFJUTW5MVEJhSm1oWDA5SmJLNGhxcjNnbFlHS1o3ZUZZVlE1U1BGaC9CWUlOTmhyWjZYazNsOHZudHkvQTBmNzFXd2NWcnpaUHlrQy96RWYrQ1dBVUVJdmtXelRnNis2ZFM3ZFI5N1FTdVd6VnlPZUF3Znp4ckZJaA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=czB5SG9hL0ZLaXY4dEJMdy9yQVZvckZTUmNaOU5MWW0yZjgxR3BZeUpIWUFxWHhNOGFwcDU0K1ZJYVdQL0ViUg=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 3",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZExFWGtXbkhCbUFBN1Z1ZEZJRXEvQ1BNVUZuZkczWXFVVjVFWjBoc3FqTDlrVklPT0VMNkRTbGpOenA2K3pzTWV2cnpqeUNBb2hsT0FvVmJmZDlSdFYxZkhaZGhPTldVakpBVzlQeXJsVTRMWXZDaHJIdzU2N3RPMDV5M1ZDaCtGQkFneE56QWp4Sk5QWndBUDY1QmZ6Zw==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=K1VYdWloUnluSThFeFJiQkxkWS85VFJvT1JkMyt1MUNDYUZTUkthYXdzMDdDUS83WEZ3VXdiOU9SakRTODg0OQ=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 4",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEtlV0Z0ejRaQS9vT0dwMWNGZzhQR2RlSnR2Y09PQy9BTlc3RGVSMjUwMEVJT0lVV2oremZsV0JSd0s1SThqTFJIbEk0SERxM2pDQUZTTEV5R3dsVXk0WnNUbXIrNHl5YmdhS00yRWJRSUpnbDllUzRDWVlZalRjcEZZcGd3UmdvRitabnNTcHlZVWJBZWtTYzlTcjNxMQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=dXZEZFVPeXNnbzkzNjJPMFM5K1dkbGZGNXY5RzZZODYrRTlsZ2lCYzl5UkhWRTZXYTlBbjYvV0RiQUFLNFhoUA=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 5",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEsrVkd1SkIyNnBnTVp2ZEJTcGJTMGNLem1Ja0JuMHlDUHQ1QTJYa20zVWdwL0I4VFhkaVNEdWNDZ2l2RkphWG1OQmYrWmhnRVhVL0V0RDBNRHZhNFNyU0JqWlV6K1FiMmxFVCs5N2p2YlV4eEh2RHUra0lrOGtkY3hRL1k2Sm1qZmlxRkROWEJ5cWw5bVMzYjMrbWt0Yw==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=TUNyZGxZb1NRN3lseTZGRjFFRnhuY0dLUEhNL3VBN0txMElqVWF3eDJrS3ZXUy9GUHIxTXB6ZUduRlB5eHRITQ=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 6",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEwxdklkdSttMFBENTBOTGlkOHE2Qm5zbU1VUlM1WFFqSnB6NkRRd2xTY2F2WVpaNGkzY0xzelZ1Wk9RNXZRTUR0MTJWWDRCZm82bUd2T1RDUllzZzcxNER0cXQyTzlaTlhNWmVCaENhcnRqeDRCM01rbU5UdmpweVl4cnVuWWFjK3NncnBlVlRiTHEvbHZYL3JzSTEwaQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=bzJpL3ptZ1V3UTJEMHRrcGJqVlVEeDJOdFp6S2pGY3VFSjd6ZUNXSmZKT0N5L1pGZFFkMGtnbW0vZlRsN2swbg=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 7",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZElVS0FUQWdMcm5hVTE4Njh5NlVBMTFzMjJxcEsya0ZzT1pRMkZ6N092bWZUbTg5UHJPV1pPdm9YQ0tpNnNJNldnU01BOWppaUdTaW5zWVhQaU9qMXFiMVVISzZZNG8rTzY3WjI4Y2JQZStudzRGa3MxSmtKbWJXRTg0dmp2M0NENTNJaGtYZlUvNzhNemt2RFFsTEhlKw==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=S213TDNGWVduQzE5SnVvS3Y2VWtLeWlkTmpNMlEzMUswb3NiQ1ZUL3h2RGorcXNmQm0xTjBWRzJwSTdxVWkzVQ=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 8",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEl4bG05d2IzR05OLzNhY3VXYjVwL2xtZWNvanhmYVVCVk9mcklVdi96RW5xUW8wODMwZ1YwOEJibjgwaVZreU1ndGgxbzhJUUF4TTNXN2kvRVR5Sm9kY0VDdTlhenp0OGk5M1d1UzhhQTRhWHRNOUo0djNiQzFzNDExMXRuL05zb1Q2Y3ZhckJmcTV6QmhqOGxDRkUvOA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=SmJudHFnUjBpTnFUUjhDYWZuaU4vSXBrN2xzUUYyb0ZiN3ZTeGlTS1N4UzF4Q0xLc2VHK1FoK1ZGSkQ5dXU5TQ=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 9",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEx1akRqKzRTOXIzNUVzbE1VVEtNSGt4OVJDV0xnTTkyVlo0S09udFcrbmU0cko4R1NWM2hGWmV6UXRvV01lQ3hxN1V3L3VKY3Q3YlMxazRZRHBNRSs4Y1RRQ2owZ1VjelJESGp3WXRiRGdmcGVOc21YVlhuT21QQW94MFhONy9KWTcyVnVtNnExTUlKTHlKNlQ0d1NIUQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=dHNReW9XQTM2emQra05ET3FZbmFrNEN4RzloUkdxQW9sYW16K0NTU2MvTWg2VUxpZTBnYTZNYW92bmdEZmtEdA=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 10",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEtpYTJKdlJ3c1F4R3grd1FDV3JpWnhPbEdyWjV2RnhQT2VEQWY4SXE1K0NVajI1aFgvK011am5JUWdPaXo5K0gvRVZGWDJiaGZUZDJPVEZKRWpOOE1oUE03NlgveFlLamNLMXA2UE9seWJma3cwanZmWDFtTy9XVnZyck9DbHRPdVczSVB6K3g2TEI5aCtEa0RnM1pjQg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=Q3Y5TXdaMVpSNHpsVTRnWGpDMncxeVIrNXMxOVVmOGdNUXJadFcyb05GL2FPRFdweWlDNUZiS2srcGxTL2x5aw=="
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"quality": "Season 1 {Hindi-English} 1080p x264 Esubs [1.2GB] - Episode Links",
|
||||
"driveseedRedirectLinks": [
|
||||
{
|
||||
"server": "Episode 1",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZExZWXo0Mzh6anRBc1k2OWljeXFiYkdXSHEyV1lYMFdDVk10VTNkNkJCanZoSEtWd2g3aFYxdERITkx6OUlsalErYWRKcGtCVFNUbjFGVWEyLzNva251a2tmODhYZUZOallmN21TVFBVaTEzSWhwbGlVV2ZvSGtSTTBqZk5uMm9hdENIZmI5VjBwRXVSY2xrajQ5Ni9ZVQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=KytGSDUxVkV2Y2RSRUxoTXhwWnNYNlJBa3JWU29EeEs3cFFCZkxOTWQ4Vy9rVkZXV0pOMHU1NEJ5S2JvbVY2VA=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 2",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEx5ME9yM1lWdHIvTGNRejJnSDB1Y0hGVXg5UXMxblU4VEh4aVFBUVFVNk5US3FEYXVEdW1iaEN3NkdyN2FyaDNucGxGbThtQ3VSa0IxN1hmWElsditkRUJpei9aWVA0RWpHd1dOQWQyOVM4Sk1FTzQyYlRYZ2V6eHUrTjVDWVZlTmFmdG5yMjAwdnoyUGs1VHhwWGZYMA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=VEdRMXJPeHVSZXJTckY0eUxjdDJ1TThKQVNvVUUySWRxdjBwUjNDRGU5SUc5TnlHZzRobWhmay9DOG9Bdnd6OQ=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 3",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEpwRStQMTBhangzQkU1MW9lcXB1RUlzMHh1WTBrdmVjaHpqMlpuYkEraDlUQ0gybmV1WlVjQ3cwd3p1ZDM5MmZhV0NIUEpoWWM2dGZxczRIZnZNQkV5VmVZMVF6VWd2STFUM3E1WWdBb2swUCtUUmZzMEoxSTY5OG1Xa29RRlR4bjF4RzBBeHlsSkxGY3hrM0s2WHBEdQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=NDRCaWx1RTM3SWNmN3QrSE9iQ0pKQUJoNzh6VGVlYVBKdmZVUDcwVXJGOGJ6TzIycDI5cnVNY0NzU1lwc1Znbg=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 4",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEwrN2w0MHdjTHpTenJzRjByVlU5eUh1WEhCbDhaYzc5M2I0S1lsS3Z6Qy94OEtkaU1ybnlmNFV1VkFaVVUwMHUwSmkzZzJ1ZjNydkRYQTNFdWpMUWI0cit0S0JHT1Fwd1VibFNxRUNoL0NJVjBBTGpiTGFBUFhvcGQxY2xJT0VnU1o3ejQrQnNzZFVqdHR0MGhHTjRpNA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=a21MUG5FTFFlZURiZXRRaU5kS0pZYWNodFlMWUl0K09CU080WDhhMnFWbEp0YnF5d0lSaGRJRGw2U2d0bDIxNw=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 5",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEpDaTVoNkQvOVI4UnRNRzF3S3BYOWgrV25md0xnUzhINHZQbDZyV0xOallqb1FOeVRWaVpjWExvY0hFejhKVjFxVjJvb3Z2SUc2ZWNiaVowYWdxZVFOVWl3SXV6UlIyaWloOGFmTGx6SGdLWjNBWGZ6N2JRVGVPNDlWSlNpcHN3YkhuMEFpM3hzdjU3cHE1TTd5NzJqRQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=MUMzWENQeGFWUzhlOXpWNkQ0ekNqR0RuQUJPNG5xZktiSlZnbFFodkJpczY4Mm4vbTB0dUU1ZERWcjhvSVhWRg=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 6",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEpnd2pDOTBlVWhhUTZzOEJRTWhERVlTWkJ2Z0J1MTRFRisxdGczQ2VXSTY4dmNyeDRxMi9UU1Y2ckRsRllnL0wzN0MrZFVKZWVsQmYxdjFMYmhhVjd5SnplL003bnFlbXRFLzJZSjJSWjR4SVNPUlpULzZNYWd0TG9NWWhmemk2MzZqcHd5Qk9wVENRd0JxeHRzRlRmQg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=WHByMlZFUCtUVkhMSkFEaTY5UmtmNWQzRWxuRVVsZWZWNzJmVEE5dlg5NjgzUXlKeDNVbnZmVCt2VjZFNnZ0dw=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 7",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEowUFJJRWdlWHl6TGZnUXJuczZhL3RPdVV4R1ZzTEJydE1xTUJxTXFSSmtaWkoxd2lReEl5bER1cmRBQ0ZETisrSHZ3MEp3QXRnRVZCd1dPdEMyWHltUGgwUURMbnhvbEFzY0xZMjhLUWIvMi9FOGNkR20vdnhDOFd4U0s4dXdzTkJxODBJbi9IRzJmVitPTXYxZUZGQQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=VXQzT3VWZ3R3NStPYXlOTDBNSjNEYkpVYmJnM1k1VTg1WXpldUEzMEZoWWtJWXdVUncrUEduWCtWeEp1Qi9nUA=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 8",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEpJNzhNdUxFQmM4bXA5Qm51eU14RDdFZzVhSEwwNE1weHZPajRzSEJwQk51djJsVnZJZGZQY1JZQmRvdHdjZ1RyS1RMUVRGZEVwYzdndUE3dUpudDAvMkV2bEo3NW0xRmRLMjVjWHJRR2I1Z2NyVVZPSTQvWkxZQi9neEtPZG4ybXRNdk9xaGUxNVdSNUpyMUhLNmVPUw==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=dTJYSExoSXBjTGZkTTBjZWNINUNuVFVLMlZEazR2SWE2cjdpWFVzd09VY1FpRFFjTytOWVV6MGt4a2pCazgxZg=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 9",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEpJSTROcHA5UmV4cXhjYTFmQVMvZUNKZmNtV2lHYnpHakFiaEJLSDRseUdYa3BNdmN6R3loOUEvYlViNlExS3JnM0ZPK1JpSzY1SHpuN2s2MDZuYkh0a256YnhyUnQxU2VaWlVDV1Y5cVo1VUlnMGxIWmRaY2I0TUR1SHJUQ0pvUUNrdys4VWp0TWhha0p2bSs5TjQ2WQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=enB3enJJeE9HeG04cXBoVkJhL1V3ZWQyVlpPU0Q3NHRXUWJiWmlFVGdCZlN0UGtaalFwYnNsMkh5Mm0xT29jRg=="
|
||||
},
|
||||
{
|
||||
"server": "Episode 10",
|
||||
"url": "https://tech.examzculture.in/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEtraXJtMzcwWWYzL3F5M2YzcjRFOUxIenVQY3dvTld6Sk9pakxocHpaSDEyNDNOVHZ2WUVUNnRCZTIxVEgwbTBQZzRJSU1BbkozSEpJa0FXbzdPSFlNZzREUVlsaUNDa0Nxak1CSlVlSUN4ZE1kWUNvU2hwVjVwdVJvSjFqcGNPaXplakE5c1lxM0pnNXh5OFEvc09ZVg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=QmpLVFI1c3U2bnM3bTEwQU4wV3E1WnFMa3RxMVhscS9la2NqQUhjTmhjdXZXVEx5Mm9Xa1lsNjlSVVpZajdvYQ=="
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"quality": "720p",
|
||||
"driveseedRedirectLinks": [
|
||||
{
|
||||
"server": "✅ Fast Server (G-Drive)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEtpNjQ1ZFhZeDl4NlZVcm9rQ2lsL0hCM0k3VytEUzRpWWMvZ1FBZlNqRDhrTE5wQVdjeHNQYzJiRG5FMDJWd3lGSXc1LzFPT0hVWFlaWTBZOGw3aDN0V1lkNDVpK0NZVjFXLzh0UGZLSzUydUU2VW83U1YwRWJvdkhVMnh1MVk0Sk9tUm9Ra2FHWVpCQXZpS3R1RGdlNA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=MkR2QTY2K0pua0xqOS9xa0pmNVIycmNiTndtQzBxbGJ5VU5vVnU0eTh3RU9ML3AydUlJU214bUV5cGx6Rlc2cw=="
|
||||
},
|
||||
{
|
||||
"server": "🚀 Google Drive (Server 2)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEo1Qm4wbW1FSnJlWkpuWWoya0JZU3VPbXVCMTdlOFNlTEpycW9nUUpPN1Ric0lnbm5mVGQxS1FGdGFjbHZ5WHN1VllwMS93QXdoNzdYdE15Z1VuQkl5TStmTUpaVzA2dWdxSEZ2WmhxaFZaS3JML0RxQ1pTNlY2eUdzbjcrMFBBSit3TnlGRmNlamxvQXJISTNNS1Y2RQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=RURQY0lpbE1IWlNaZDhkNld1MFFnSEQ4V0xXNFJEeHZNNGFSZ3lnUW54aDZTLzUrRXdhVVRLaEhITno1ejFYdw=="
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"quality": "1080p",
|
||||
"driveseedRedirectLinks": [
|
||||
{
|
||||
"server": "✅ Fast Server (G-Drive)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZElqenM4UmhUeEJKTlpncjJnbVR1aldlTmZIb0kzcStJdm9vMGx6UmtGWUtKMjBWMXdYM1o5U3pseEJJMEMvN29McW9qeXZrZTVEa1F1ejlFbUgwRFBJb3dDTTV5TVoyVktJOGk3eEZYUys3Y29sQW5GcmtqaXZEMk9MSk1JWm9JSVlVTWdoSWdVQWNiQnpEYXp6Q054Vw==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=dHV1dW9KeTlVMEM0UERzaVFaRWVTelN2TlVEUG5IUFdsSFdDYk4vZ29WamhPZThPcTBRRk9oc0lhTnppSlN0ag=="
|
||||
},
|
||||
{
|
||||
"server": "🚀 Google Drive (Server 2)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEtSYVErY0dNVXJDenhFZzFHZ05mR0ZOWWJ1eWJCNEgvdWtFRTlwY3orekF4QS84LzR1enRuTnpxMFZLT0ZGYkxVdXNuTHNWUklUUDh0SEkrYlYrbHI5MUFRcUo1UWNRSm93b2FScnA0YVU5T3dFeENHa2xHcjhYSXNBcXpodkE0alpOMW85L0ZoVS9MSzRFY2trS0NnTQ==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=dVNjL3hNRXhseGRrenJhQzFwQ24yekphRXhyL1JtUzBxUXlCTVgzUVNOSHRmbzhKK05BU3FjT0Q3MFhrMzlRYw=="
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"quality": "720p",
|
||||
"driveseedRedirectLinks": [
|
||||
{
|
||||
"server": "✅ Fast Server (G-Drive)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEsrRk5KdUcxZG1IblE5T2d2cGowNW9mT2dLd3ZHK0tzeXp1TnJVZGs0cGs1Mmp6d1kyK1lMY1lERitNL2dlNUtlMGJxa0JaYncrRFNzeVlZOEo2QUJLc0RWTGtuRVI2TTZ4Mk1hUVZCQTVlTUo2d3FmcUVrbUFOQnY1RUNFd3J1TmxHTnQvVE5yL1BoNkkwaHVrdVBiZA==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=ZjU4U21qeFVnN2x1RDhVbm55VHFJUWU1b2JHVEljRm96QzcySzV0NFZqRU1HU05uSlN5ck00TUs3bnFRS1ljWg=="
|
||||
},
|
||||
{
|
||||
"server": "🔗 Gdrive Links (Login)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZExBSThhUFBxd1BlaE81V0UwOCtVUGphK2lFL00xNXJ1OEQyaGJHckRxSi9SelBiNTFMQ3IzNENPWXhoeDg0SjRGMmo4WTFLMVYyOGhRWXVjSDNkajAramZtMHF6dHFpd2NqZGYwWTE3aUxqcGxYbHJyc2xHWUw0anNXUmpUS3E0SFVZc2cyU0RJVEl0Ky92SThreVVLdg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=Z2luaGp1aXZ4WXkwSnFBU3pyZWxiU21Mb015bldhdTkzd1JTMkY5bU1CVVB3Q05sUTd1NlRKbHZJSUNnanREUA=="
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"quality": "1080p",
|
||||
"driveseedRedirectLinks": [
|
||||
{
|
||||
"server": "✅ Fast Server (G-Drive)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEpxbCtGRHB6dmUrTUp2ZUl2R3pHVGhyaDFTVUJCTDhsTnBTbXBmSFRwZ3BVUk5tS0xGRVJJd245eFgzanlKZ0Q4SzlPakFDQzNZZHhBdFVleFNCL1p4UGpxd1hFWUdXNXBWSm5XQ1FQWERXcDE1RSsrTjBVNGVmeHR6aDFwbGNrbkJSL004T1d2dTJ2MHlrYkhGSmlIQg==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=alBtU2J0bGprbXYxbzhJcmwrTXc3ZjhDMFhFV0xxZ25ocUliR2NZbkcxMkp5RER2QmlMNjNBWmxEZXhoeUpaRA=="
|
||||
},
|
||||
{
|
||||
"server": "🔗 Gdrive Links (Login)",
|
||||
"url": "https://tech.unblockedgames.world/?sid=VWNwbWlPV1lMWFcwa1ZDb1dxQVBZcExWbFhIM3l3TExmMjg5TGlNcDcrSmhRZmlMWEs1b0pXRUlnRllnckQ5dWdJTDRiOGNLM3N1WWx5d0Y5Vk16SGl1MHN3ZDkzUnpPVW1vRUM3OFNRZEpPSnl1Ri9xazhmcVUwakZydFg2NFZVUzJRbUxqdjkvSktycFBKWmhML2F5VXpYMCt6c1draXQxVGYvWEhLK0xwNVJVTVZQSlRiWUtHTUd2TmNuSytybHd0czhlcFdwM3cvZnZnZ1YrcmNhTUdDSUxabU1nZ04veXlCYUIrSFIvbkJmSWpmSkFkOFVHTDJMVk5ibkw4Lw==",
|
||||
"driveseedRedirectUrl": "https://driveseed.org/r?key=K09xNUEvbnBzaGh3Ym94RVQ2QW5aakdHV240a1M3RjhuRUViSHlYenpPVT0=&id=Q0o2RUdsdE9iZDhIYlQwMXlRdjEyMlA1NEVKZkRmRUNYZTcwdjNGa3RPa0xHZ29Na0taTkM4T2ZjWGZmRWxkQw=="
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"showboxUrl": "NO_URL_FOUND",
|
||||
"timestamp": 1757925353144
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"showboxUrl": "NO_URL_FOUND",
|
||||
"timestamp": 1757926777078
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"showboxUrl": "NO_URL_FOUND",
|
||||
"timestamp": 1757926668402
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"showboxUrl": "NO_URL_FOUND",
|
||||
"timestamp": 1757950186614
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"result": "NO_MATCH_FOUND",
|
||||
"timestamp": 1757950186613
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"result": "NO_MATCH_FOUND",
|
||||
"timestamp": 1757926668401
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"result": "NO_MATCH_FOUND",
|
||||
"timestamp": 1757926777077
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"result": "NO_MATCH_FOUND",
|
||||
"timestamp": 1757925353143
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"id": 1263256,
|
||||
"titles": [
|
||||
{
|
||||
"iso_3166_1": "VN",
|
||||
"title": "Chàng Happy Gilmore 2",
|
||||
"type": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": 1263256,
|
||||
"imdb_id": "tt31868189",
|
||||
"wikidata_id": "Q130269576",
|
||||
"facebook_id": null,
|
||||
"instagram_id": null,
|
||||
"twitter_id": null
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"adult": false,
|
||||
"backdrop_path": "/x5dVPttNDZaVRTvbk7pYrtGZoZN.jpg",
|
||||
"belongs_to_collection": {
|
||||
"id": 1263259,
|
||||
"name": "Happy Gilmore Collection",
|
||||
"poster_path": "/7nUOrnZHSgnhagLtydIYbD4rrDY.jpg",
|
||||
"backdrop_path": "/mhrE9ij30cIvPFVFRXZSwwTchUg.jpg"
|
||||
},
|
||||
"budget": 152000000,
|
||||
"genres": [
|
||||
{
|
||||
"id": 35,
|
||||
"name": "Comedy"
|
||||
}
|
||||
],
|
||||
"homepage": "https://www.netflix.com/title/81696722",
|
||||
"id": 1263256,
|
||||
"imdb_id": "tt31868189",
|
||||
"origin_country": [
|
||||
"US"
|
||||
],
|
||||
"original_language": "en",
|
||||
"original_title": "Happy Gilmore 2",
|
||||
"overview": "Happy Gilmore isn't done with golf — not by a long shot. Since his retirement after his first Tour Championship win, Gilmore returns to finance his daughter's ballet classes.",
|
||||
"popularity": 24.4786,
|
||||
"poster_path": "/ynT06XivgBDkg7AtbDbX1dJeBGY.jpg",
|
||||
"production_companies": [
|
||||
{
|
||||
"id": 878,
|
||||
"logo_path": "/s5j0xnTXvglZoz49CUaQclVv3s9.png",
|
||||
"name": "Happy Madison Productions",
|
||||
"origin_country": "US"
|
||||
}
|
||||
],
|
||||
"production_countries": [
|
||||
{
|
||||
"iso_3166_1": "US",
|
||||
"name": "United States of America"
|
||||
}
|
||||
],
|
||||
"release_date": "2025-07-25",
|
||||
"revenue": 0,
|
||||
"runtime": 118,
|
||||
"spoken_languages": [
|
||||
{
|
||||
"english_name": "English",
|
||||
"iso_639_1": "en",
|
||||
"name": "English"
|
||||
}
|
||||
],
|
||||
"status": "Released",
|
||||
"tagline": "Happy Gilmore returns!",
|
||||
"title": "Happy Gilmore 2",
|
||||
"video": false,
|
||||
"vote_average": 6.673,
|
||||
"vote_count": 712
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
{
|
||||
"id": 1893,
|
||||
"titles": [
|
||||
{
|
||||
"iso_3166_1": "US",
|
||||
"title": "Star Wars: Episode I",
|
||||
"type": "working title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "US",
|
||||
"title": "Star Wars I: The Phantom Menace",
|
||||
"type": "video box title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "CZ",
|
||||
"title": "Skrytá hrozba",
|
||||
"type": "short title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "FR",
|
||||
"title": "La menace fantôme",
|
||||
"type": "short title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "FI",
|
||||
"title": "Tähtien sota - episodi I: Pimeä uhka",
|
||||
"type": "TV title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "DK",
|
||||
"title": "Den usynlige fjende",
|
||||
"type": "short title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "RU",
|
||||
"title": "Скрытая угроза",
|
||||
"type": "short title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "ES",
|
||||
"title": "Star Wars: Episodio I - La amenaza fantasma",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "US",
|
||||
"title": "The Phantom Menace",
|
||||
"type": "short title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "HK",
|
||||
"title": "星球大戰前傳:魅影危機",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "US",
|
||||
"title": "Star Wars: The Phantom Menace",
|
||||
"type": "iTunes Title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "TW",
|
||||
"title": "星際大戰首部曲:威脅潛伏",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "KR",
|
||||
"title": "스타워즈 에피소드1 보이지 않는 위험",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "HU",
|
||||
"title": "Bajlós Árnyak",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "BR",
|
||||
"title": "Star Wars: A Ameaça Fantasma",
|
||||
"type": "short title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "NO",
|
||||
"title": "Star Wars: Episode I - Den skjulte trussel",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "FR",
|
||||
"title": "Star Wars - Episode I - La Menace Fantôme",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "GR",
|
||||
"title": "Ο Πόλεμος Των Άστρων: Επεισόδιο 1 - Η Αόρατη Απειλή",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "CA",
|
||||
"title": "Star Wars : épisode I - La menace fantôme",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "CZ",
|
||||
"title": "Hvězdné války: Epizoda I - Skrytá hrozba",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "KR",
|
||||
"title": "스타 워즈 에피소드 1, 보이지 않는 위험",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "FR",
|
||||
"title": "Star Wars : La menace fantôme",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "SK",
|
||||
"title": "Star Wars: Skrytá hrozba",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "SK",
|
||||
"title": "Hviezdné vojny: Epizoda I - Skrytá hrozba",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "RU",
|
||||
"title": "Звёздные войны: Эпизод I - Скрытая угроза",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "RU",
|
||||
"title": "Звёздные войны: Скрытая угроза",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "UA",
|
||||
"title": "Зоряні війни: Прихована загроза",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "IT",
|
||||
"title": "Star Wars - Episodio I - La minaccia fantasma",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "DE",
|
||||
"title": "Star Wars Episode 1 - Die dunkle Bedrohung",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "JP",
|
||||
"title": "スター・ウォーズ/ファントム・メナス",
|
||||
"type": "iTunes title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "KR",
|
||||
"title": "스타워즈 에피소드 1 보이지 않는 위험",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "US",
|
||||
"title": "Star Wars: The Phantom Menace (Episode I)",
|
||||
"type": "Disney+"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "CN",
|
||||
"title": "星球大战前传:魅影危机",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "PL",
|
||||
"title": "Gwiezdne Wojny I - Mroczne Widmo",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "TW",
|
||||
"title": "星球大戰前傳:魅影危機",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "FR",
|
||||
"title": "Star Wars, Episode I - La Menace fantôme",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "CN",
|
||||
"title": "星球大战1:幽灵的威胁",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "CN",
|
||||
"title": "星球大战前传1:幽灵的威胁",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "CN",
|
||||
"title": "星球大战前传:幽灵的威胁",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "CN",
|
||||
"title": "星球大战:幽灵的威胁",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "CN",
|
||||
"title": "星球大战前传1:魅影危机",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "CN",
|
||||
"title": "星球大战:魅影危机",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "FR",
|
||||
"title": "Star Wars, épisode I : La Menace fantôme",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "HU",
|
||||
"title": "Csillagok Háborúja I. - Baljós Árnyak",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "GE",
|
||||
"title": "ვარსკვლავური ომები: ფარული საფრთხე (ეპიზოდი I)",
|
||||
"type": "Disney+"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "GE",
|
||||
"title": "ვარსკვლავური ომები: ეპიზოდი I",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "GE",
|
||||
"title": "ვარსკვლავური ომები I",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "GE",
|
||||
"title": "ვარსკვლავური ომები 1",
|
||||
"type": "short title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "GE",
|
||||
"title": "ვარსკვლავური ომები: ეპიზოდი I - ფარული საფრთხე",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "GE",
|
||||
"title": "ვარსკვლავური ომები: ეპიზოდი 1",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "IL",
|
||||
"title": "סטאר וורס פרק I: אימת הפאנטום",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "VN",
|
||||
"title": "Chiến Tranh Giữa Các Vì Sao 1: Hiểm Họa Bóng Ma",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "ES",
|
||||
"title": "La amenaza fantasma",
|
||||
"type": "Titulo abreviado"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "MX",
|
||||
"title": "Star Wars: La amenaza fantasma",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "JP",
|
||||
"title": "スター・ウォーズ/ファントム・メナス (エピソード1)",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "US",
|
||||
"title": "Star Wars Episode I - The Phantom Menace",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "DE",
|
||||
"title": "Star Wars - Episode 1 - Die dunkle Bedrohung",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "IL",
|
||||
"title": "מלחמת הכוכבים 1: אימת הפנטום",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "SG",
|
||||
"title": "Star Wars Episode I:The Phantom Menace",
|
||||
"type": "HK & Singapore Style English Asian Font Set"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "UZ",
|
||||
"title": "Yulduzlar jangi-1: Yashirin tahdid",
|
||||
"type": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": 1893,
|
||||
"imdb_id": "tt0120915",
|
||||
"wikidata_id": "Q165713",
|
||||
"facebook_id": "StarWars",
|
||||
"instagram_id": "starwars",
|
||||
"twitter_id": "starwars"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,66 +0,0 @@
|
||||
{
|
||||
"adult": false,
|
||||
"backdrop_path": "/wDe8LzwuvHYYiuwyNfxdYQq8ti4.jpg",
|
||||
"belongs_to_collection": {
|
||||
"id": 10,
|
||||
"name": "Star Wars Collection",
|
||||
"poster_path": "/22dj38IckjzEEUZwN1tPU5VJ1qq.jpg",
|
||||
"backdrop_path": "/4z9ijhgEthfRHShoOvMaBlpciXS.jpg"
|
||||
},
|
||||
"budget": 115000000,
|
||||
"genres": [
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Adventure"
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"name": "Action"
|
||||
},
|
||||
{
|
||||
"id": 878,
|
||||
"name": "Science Fiction"
|
||||
}
|
||||
],
|
||||
"homepage": "http://www.starwars.com/films/star-wars-episode-i-the-phantom-menace",
|
||||
"id": 1893,
|
||||
"imdb_id": "tt0120915",
|
||||
"origin_country": [
|
||||
"US"
|
||||
],
|
||||
"original_language": "en",
|
||||
"original_title": "Star Wars: Episode I - The Phantom Menace",
|
||||
"overview": "Anakin Skywalker, a young slave strong with the Force, is discovered on Tatooine. Meanwhile, the evil Sith have returned, enacting their plot for revenge against the Jedi.",
|
||||
"popularity": 7.5815,
|
||||
"poster_path": "/6wkfovpn7Eq8dYNKaG5PY3q2oq6.jpg",
|
||||
"production_companies": [
|
||||
{
|
||||
"id": 1,
|
||||
"logo_path": "/tlVSws0RvvtPBwViUyOFAO0vcQS.png",
|
||||
"name": "Lucasfilm Ltd.",
|
||||
"origin_country": "US"
|
||||
}
|
||||
],
|
||||
"production_countries": [
|
||||
{
|
||||
"iso_3166_1": "US",
|
||||
"name": "United States of America"
|
||||
}
|
||||
],
|
||||
"release_date": "1999-05-19",
|
||||
"revenue": 924317558,
|
||||
"runtime": 136,
|
||||
"spoken_languages": [
|
||||
{
|
||||
"english_name": "English",
|
||||
"iso_639_1": "en",
|
||||
"name": "English"
|
||||
}
|
||||
],
|
||||
"status": "Released",
|
||||
"tagline": "Every saga has a beginning.",
|
||||
"title": "Star Wars: Episode I - The Phantom Menace",
|
||||
"video": false,
|
||||
"vote_average": 6.566,
|
||||
"vote_count": 15219
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
{
|
||||
"id": 278,
|
||||
"titles": [
|
||||
{
|
||||
"iso_3166_1": "SE",
|
||||
"title": "Rita Hayworth - nyckel till flykten",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "CN",
|
||||
"title": "肖申克的救赎",
|
||||
"type": "Mandarin title"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "GE",
|
||||
"title": "Gaqceva shoushenkidan",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "AZ",
|
||||
"title": "Şouşenkdən qaçış",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "RU",
|
||||
"title": "Искупление Шоушенком",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "AZ",
|
||||
"title": "Shoushenkden Qacish",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "IR",
|
||||
"title": "Rastgari Dar Shawshank",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "IL",
|
||||
"title": "Homot Shel Tikva",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "JP",
|
||||
"title": "Shôshanku no sora ni",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "RS",
|
||||
"title": "Бекство из Шошенка",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "KR",
|
||||
"title": "쇼생크 탈출",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "FR",
|
||||
"title": "Les Évadés",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "PL",
|
||||
"title": "Skazani na Shawshank",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "JP",
|
||||
"title": "ショーシャンクの空に:1994",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "IR",
|
||||
"title": "رستگاری در شاوشنگ",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "KR",
|
||||
"title": "쇼생크탈출",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "BR",
|
||||
"title": "Um Sonho de Liberdade",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "TW",
|
||||
"title": "刺激1995",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "AM",
|
||||
"title": "Փախուստ Շոուշենքից",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "AR",
|
||||
"title": "Sueño de Libertad",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "BY",
|
||||
"title": "Выкупленне з Шаўшэнку",
|
||||
"type": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": 278,
|
||||
"imdb_id": "tt0111161",
|
||||
"wikidata_id": "Q172241",
|
||||
"facebook_id": null,
|
||||
"instagram_id": null,
|
||||
"twitter_id": null
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"adult": false,
|
||||
"backdrop_path": "/pNjh59JSxChQktamG3LMp9ZoQzp.jpg",
|
||||
"belongs_to_collection": null,
|
||||
"budget": 25000000,
|
||||
"genres": [
|
||||
{
|
||||
"id": 18,
|
||||
"name": "Drama"
|
||||
},
|
||||
{
|
||||
"id": 80,
|
||||
"name": "Crime"
|
||||
}
|
||||
],
|
||||
"homepage": "",
|
||||
"id": 278,
|
||||
"imdb_id": "tt0111161",
|
||||
"origin_country": [
|
||||
"US"
|
||||
],
|
||||
"original_language": "en",
|
||||
"original_title": "The Shawshank Redemption",
|
||||
"overview": "Imprisoned in the 1940s for the double murder of his wife and her lover, upstanding banker Andy Dufresne begins a new life at the Shawshank prison, where he puts his accounting skills to work for an amoral warden. During his long stretch in prison, Dufresne comes to be admired by the other inmates -- including an older prisoner named Red -- for his integrity and unquenchable sense of hope.",
|
||||
"popularity": 31.7568,
|
||||
"poster_path": "/9cqNxx0GxF0bflZmeSMuL5tnGzr.jpg",
|
||||
"production_companies": [
|
||||
{
|
||||
"id": 97,
|
||||
"logo_path": "/qv3ih9pR9w2XNKZDsqDqAGuZjqc.png",
|
||||
"name": "Castle Rock Entertainment",
|
||||
"origin_country": "US"
|
||||
}
|
||||
],
|
||||
"production_countries": [
|
||||
{
|
||||
"iso_3166_1": "US",
|
||||
"name": "United States of America"
|
||||
}
|
||||
],
|
||||
"release_date": "1994-09-23",
|
||||
"revenue": 28341469,
|
||||
"runtime": 142,
|
||||
"spoken_languages": [
|
||||
{
|
||||
"english_name": "English",
|
||||
"iso_639_1": "en",
|
||||
"name": "English"
|
||||
}
|
||||
],
|
||||
"status": "Released",
|
||||
"tagline": "Fear can hold you prisoner. Hope can set you free.",
|
||||
"title": "The Shawshank Redemption",
|
||||
"video": false,
|
||||
"vote_average": 8.7,
|
||||
"vote_count": 28866
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"id": 293660,
|
||||
"titles": [
|
||||
{
|
||||
"iso_3166_1": "VN",
|
||||
"title": "Quái Nhân Deadpool",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "CN",
|
||||
"title": "死侍1",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "ES",
|
||||
"title": "Deadpool: 1",
|
||||
"type": "Castellano"
|
||||
},
|
||||
{
|
||||
"iso_3166_1": "DE",
|
||||
"title": "Deadpool 1",
|
||||
"type": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": 293660,
|
||||
"imdb_id": "tt1431045",
|
||||
"wikidata_id": "Q19347291",
|
||||
"facebook_id": "DeadpoolMovie",
|
||||
"instagram_id": "deadpoolmovie",
|
||||
"twitter_id": "deadpoolmovie"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,84 +0,0 @@
|
||||
{
|
||||
"adult": false,
|
||||
"backdrop_path": "/en971MEXui9diirXlogOrPKmsEn.jpg",
|
||||
"belongs_to_collection": {
|
||||
"id": 448150,
|
||||
"name": "Deadpool Collection",
|
||||
"poster_path": "/30c5jO7YEXuF8KiWXLg9m28GWDA.jpg",
|
||||
"backdrop_path": "/dTq7mGyAR5eAydR532feWfjJjzm.jpg"
|
||||
},
|
||||
"budget": 58000000,
|
||||
"genres": [
|
||||
{
|
||||
"id": 28,
|
||||
"name": "Action"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Adventure"
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"name": "Comedy"
|
||||
}
|
||||
],
|
||||
"homepage": "https://www.20thcenturystudios.com/movies/deadpool",
|
||||
"id": 293660,
|
||||
"imdb_id": "tt1431045",
|
||||
"origin_country": [
|
||||
"US"
|
||||
],
|
||||
"original_language": "en",
|
||||
"original_title": "Deadpool",
|
||||
"overview": "The origin story of former Special Forces operative turned mercenary Wade Wilson, who, after being subjected to a rogue experiment that leaves him with accelerated healing powers, adopts the alter ego Deadpool. Armed with his new abilities and a dark, twisted sense of humor, Deadpool hunts down the man who nearly destroyed his life.",
|
||||
"popularity": 15.1616,
|
||||
"poster_path": "/3E53WEZJqP6aM84D8CckXx4pIHw.jpg",
|
||||
"production_companies": [
|
||||
{
|
||||
"id": 25,
|
||||
"logo_path": "/qZCc1lty5FzX30aOCVRBLzaVmcp.png",
|
||||
"name": "20th Century Fox",
|
||||
"origin_country": "US"
|
||||
},
|
||||
{
|
||||
"id": 431,
|
||||
"logo_path": "/6dcR1MbRqYgt3jUVYxkHe68GFnZ.png",
|
||||
"name": "The Donners' Company",
|
||||
"origin_country": "US"
|
||||
},
|
||||
{
|
||||
"id": 28788,
|
||||
"logo_path": "/Aqomtf9oh5dKtxBNEagkdlp3aGv.png",
|
||||
"name": "Genre Films",
|
||||
"origin_country": "US"
|
||||
},
|
||||
{
|
||||
"id": 7505,
|
||||
"logo_path": "/837VMM4wOkODc1idNxGT0KQJlej.png",
|
||||
"name": "Marvel Entertainment",
|
||||
"origin_country": "US"
|
||||
}
|
||||
],
|
||||
"production_countries": [
|
||||
{
|
||||
"iso_3166_1": "US",
|
||||
"name": "United States of America"
|
||||
}
|
||||
],
|
||||
"release_date": "2016-02-09",
|
||||
"revenue": 782837347,
|
||||
"runtime": 108,
|
||||
"spoken_languages": [
|
||||
{
|
||||
"english_name": "English",
|
||||
"iso_639_1": "en",
|
||||
"name": "English"
|
||||
}
|
||||
],
|
||||
"status": "Released",
|
||||
"tagline": "Feel the love.",
|
||||
"title": "Deadpool",
|
||||
"video": false,
|
||||
"vote_average": 7.623,
|
||||
"vote_count": 31904
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"tmdbId": "278",
|
||||
"tmdbType": "movie",
|
||||
"title": "The Shawshank Redemption"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"tmdbId": "1893",
|
||||
"tmdbType": "movie",
|
||||
"title": "Star Wars: Episode I - The Phantom Menace"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"tmdbId": "1263256",
|
||||
"tmdbType": "movie",
|
||||
"title": "Happy Gilmore 2"
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"quality": "Unknown Quality",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzbjJiSzNaUWpVaG5QM1F2NGJZUjhRVT0=",
|
||||
"rawQuality": "Unknown Quality",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.eu/1080p-uhd/"
|
||||
},
|
||||
{
|
||||
"quality": "Unknown Quality",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzaE5qZ1JnMXNxa1hHT1JiL3ZEZ2lNcz0=",
|
||||
"rawQuality": "Unknown Quality",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.eu/movies/"
|
||||
},
|
||||
{
|
||||
"quality": "4K | HDR",
|
||||
"size": "16.68 GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2o4SUZUQjVyK3FDd1MyUXhZQlJ3TFJsdTNiUHhwd1Q4QTVGRlpUdGQxNVpPd0E2YmlWVUJ0c0lXZU9Sa0s1eXVuT2prMEd5dTVEc0xCdHl0Q0FZTTd2Nlhad1pLTnZzODg5L2tWRDR5aGp3ZEpZL014N1RRVVZRYUpWRXg5ZjRGQUtGZDZOTHJ0QVp0MitQUDY4WmVqbA==",
|
||||
"rawQuality": "Happy Glimore 2 (2025).2160p NF WEB-DL HDR 10bit HEVC [Hindi DDP 5.1 + English DDP 5.1] x265 (HHWEB-UHDMovies) [16.68 GB]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=dWFxZDQrS3RwK2dZZFVoUHBydmRxQVlORUVHQVc0NkFsRm02UDk3ai80YjZHbE85N2dOWWVZcUhrMVh3U2JONA=="
|
||||
},
|
||||
{
|
||||
"quality": "4K",
|
||||
"size": "11.14 GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2dBcGU3S01vY1JIWkxlYnRZUlpscVFDNFIxbEpDeGtTWVFxNCt3dlVsampCNlhrdVJlUXU3VkZCQVZPL0ttd0hPZ2Q3eU5KQ1VGZVI4cW5ZbWxmNUNXWEt0ejRiTUVBQ3FWQ1RZMjBmV2tPMWdZRVNnQURYZjZSWE56cjJUYjNHbnQ1OUZ2NG96VVZUclJlNlZKSGx6YQ==",
|
||||
"rawQuality": "Happy Glimore 2 (2025).2160p NF WEB-DL SDR 10bit HEVC [Hindi DDP 5.1 + English DDP 5.1] x265 (HHWEB-UHDMovies) [11.14 GB]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=UHBTVjR3T3IybXRMS0RjY01tK3FHMVJqbGNzdWVEMFczNjkvdjdpWUh2SHl5ekwwVEFnNHFNdllKRFg2Z015TA=="
|
||||
},
|
||||
{
|
||||
"quality": "1080p",
|
||||
"size": "5.28 GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2hiSUN6VHNPMnIyK1o2NHV4VkVpQmVnUmRqUmRoOFh1UkpKZkdhY3JmbWxOeXdMU1c2SVdnb0FwTnh1b2EySDVVZTVOK1hwTVk0RTIwNEgvN21BcEp4STc1OHlpbUV0Ylh6c1pyc0V5VjFQTEttS3luUXZPL0ZTQ3pycTlhYXgwcGpjWDcyZFdENmdKam5DUmxXUmFKcQ==",
|
||||
"rawQuality": "Happy.Gilmore.2.2025.1080p.NF.WEB-DL.DUAL.DDP5.1.Atmos.H.264-ShiNobi [5.28 GB]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=cW5YYzgyZ2IwdW9pWmdXakRIMTRad2xkV3l2eGxkSEZtc0FwUE8vSEs4cjJTNEZLVWIvTjZvaUVPeHQrZ3p5bQ=="
|
||||
},
|
||||
{
|
||||
"quality": "1080p",
|
||||
"size": "5.28 GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzdldUTFJiZlVQZVg0V2JWNkRGRU9uM2RPcjB5UTRtWmxvUXF0OUVLWU8yYw==",
|
||||
"rawQuality": "Happy.Gilmore.2.2025.1080p.NF.WEB-DL.DUAL.DDP5.1.Atmos.H.264-ShiNobi [5.28 GB]",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.eu/1080p-60fps/"
|
||||
},
|
||||
{
|
||||
"quality": "1080p",
|
||||
"size": "5.28 GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzcGJmM0lkMWZ4Zyt1Q2xiZ3VlQThqcXlHbHQ2ZUNaejFqbElUa1lXZm9PSg==",
|
||||
"rawQuality": "Happy.Gilmore.2.2025.1080p.NF.WEB-DL.DUAL.DDP5.1.Atmos.H.264-ShiNobi [5.28 GB]",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.eu/1080p-10bit/"
|
||||
},
|
||||
{
|
||||
"quality": "1080p",
|
||||
"size": "5.28 GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzdlJ5d0FSNm5XZjFtTlkrM3FmOFpLbz0=",
|
||||
"rawQuality": "Happy.Gilmore.2.2025.1080p.NF.WEB-DL.DUAL.DDP5.1.Atmos.H.264-ShiNobi [5.28 GB]",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.eu/4k-hdr/"
|
||||
},
|
||||
{
|
||||
"quality": "Unknown Quality",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzamFGMjVxNDJ0ZTc0RFM2M0pUNFdYTW1tRlNzbVl2Uzk0N3pDSHdoTmZFUQ==",
|
||||
"rawQuality": "Unknown Quality",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.eu/2160p-movies/"
|
||||
},
|
||||
{
|
||||
"quality": "Unknown Quality",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzbDZUM21XeXB0Tzl6TTB6c1BDN0x0cz0=",
|
||||
"rawQuality": "Unknown Quality",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.eu/3d-movies/"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"quality": "4K | DV | BluRay",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2pvMHJ3OElxMDl5bFcwWjdSL2cvQWZBdGdnVTNZUE5uSUllcDBlZUc0N0FWb25JS2J4bDNLQVRXMTUwYytoRzFodExZa3pDNE00NWVrejVSS1R3bmlyaDZwR2diVkVMUFhLVkRWTlBWazNGNURYNWczYVFEZ080WWNydkNqNHRxajJuUitjdXJEZkZiNnNrRnBrZDlyMg==",
|
||||
"rawQuality": "Game.of.Thrones.S01.UHD.BluRay.2160p.DV.HEVC.REMUX.[Hindi DD 2.0 – Eng TrueHD.Atmos.7.1]-MSubs~(Tom) FraMeSToR [25 GB/E]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=OEcxNEtwQ3ZrMFlaSEp2eFY1RGZYa1Mwa1V0NThXVGQ3T1FjZnpOM0NBY2poZjQyRFpRNmgwQ3hhZkxzeFpjRw=="
|
||||
},
|
||||
{
|
||||
"quality": "1080p | BluRay",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2oycFVobHI5cmZ3OVNWaG5RYWZEQTFyRmVkQWZ4amdycTVjU0J6Vnl1UUl5SmxPR1ZxdmJJWlRXL1doYUVKMitqN1FWdDdKdERiTUI2U3c4TVJ1M1lYdDZpMTd3WTc1bXM1WElFYVZLTGlrODFGbVNaN20wOFYzYzBIZkUyTlQvZWFRbXEvMXVndGh6dG1Ld2FwLy82OA==",
|
||||
"rawQuality": "Game of Thrones (2011) S01 1080p BluRay [Hindi DD 2.0 + English DTS 5.1] x264-LiNG[AK47] [5 GB/E]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=eUc0QUdoUmt5VkxGdTNmWk1aOVlndTFwNHZlUHl5RHNIMFBMZjFZdEdXTkc1L2lqWXlQdU96Y3ZFRXl4UTNNbw=="
|
||||
},
|
||||
{
|
||||
"quality": "1080p | BluRay",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2pSNWFKd3dwNVR2MVpRd3lqN1cwYXFxQVJiaTdHOC9rWmhqcFNVTytydVVBUFFGVEd1aUJ5YjUxLzZFaFgxU1JjU1hFTEpVdDVpMXlGTWVNMmh0aFNWb2d4TzBaTlNaOU9EM3Y4SVM3bllGSmJQR0NvZzg1TWNObEdOOWZoNFVyazhBOGk3emhxZjhGVy9RY3NpYnB6MQ==",
|
||||
"rawQuality": "Game of Thrones S01 1080p BluRay 10bit HEVC [Hindi DD 2.0 + English AAC 5.1] x265-Joy[AK47] [800 MB/E] [09 GB ZIP]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=U2lzTnlYK3lhbDg3a0JNb3NsUVZ5OCtLV1FvaysrMG5jRHJOSFpSUllrWW9qNm56UCtYdkRUcmREYlNxVkp4Ng=="
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"quality": "Unknown Quality",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMza2Z2VFBEdEhydU93VlhXT3JOYXFGaUhtU3FyeGkwbjh6d1RoajJEaW5UbQ==",
|
||||
"rawQuality": "Unknown Quality",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.mov/4k-movies/"
|
||||
},
|
||||
{
|
||||
"quality": "Unknown Quality",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzbUoyR2JpV2JxSXVOcjZLK0dGKzFhNDdlR09GT3JsaHBmcXR6cFVXeEdCYw==",
|
||||
"rawQuality": "Unknown Quality",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.mov/2160p-hevc/"
|
||||
},
|
||||
{
|
||||
"quality": "Unknown Quality",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMza3ZQQ2I2VEZZM3p6ZWFPMDhBQWRoaEh6c21YSjRDQXV5dHNFdGhNZVF3UA==",
|
||||
"rawQuality": "Unknown Quality",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.mov/1080p-uhd/"
|
||||
},
|
||||
{
|
||||
"quality": "Unknown Quality",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzdlNJRWNQTHBvd0ZXZXQ2bXQzQ083ND0=",
|
||||
"rawQuality": "Unknown Quality",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.mov/movies/"
|
||||
},
|
||||
{
|
||||
"quality": "4K | HDR | DV | BluRay",
|
||||
"size": "26GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2hGRTRnNzZRQUIrNUIxcWpDMk9xMEVRNXV2bncyNG8reHE3YVJtOUxBbjhvVzRSYTFjd2VsN2drOUlxM2VVVHd1K3YzcmRtSDg5N3FXMVRQY2hPYjhkUUFmcGlNNnVmb1hZMnJEL2R1WHQxb1dhNEJjV0U2RERKYjVEc2ZHOUk5TzZVbXlxanZHVFhFYVRsOUp0LzdYag==",
|
||||
"rawQuality": "Deadpool (2016) 2160p UHD BluRay DV HDR 10bit HEVC [Hindi DDP 5.1 + English DDP 5.1] x265 (d3g-UHDMovies [26GB]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=bFZyc1p1MTVRSU94bXo5ZmJzQlJHcDByaGYwcXZSdnZVbm84ZzVQRlFvSVlLQlFOcTAzWHFGUzB5UGh5cERDRw=="
|
||||
},
|
||||
{
|
||||
"quality": "4K | HDR | DV | BluRay",
|
||||
"size": "6GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2llRS9MRS9Md3BGVWZLWDkrQzk3N05kZVd0aFJwNFNJZGJOYjJ2c3U0aXFiVkNpQkxseWhsNkNWemZwYkk2RFRyOWh5VHJiVHFMTGk3ODU0c2xrcG82Yzk3cnJlSEU5R1JwSjBkYk8wN1JkUnJ3dE0wWCtLWHJxUXc3OVBSZEMySTg0azhDMlc4cnVUc3pDUldaQlBGTg==",
|
||||
"rawQuality": "Deadpool (2016) 2160p UHD BluRay DV HDR 10bit HEVC [Hindi DDP 5.1 + English DDP 5.1] x265 (RARBG-UHDMovies) [6GB]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=L0VucTlwOXNCdmRjNFJUL01XS2lTNDNuRTNQbGtnYWFpNk5QOUNQSEN1cm1kRUtmUy8rZDhReDUzVEk0N1k2Mw=="
|
||||
},
|
||||
{
|
||||
"quality": "1080p | HDR | DV | BluRay",
|
||||
"size": "12GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=UldoSTVjL3N3QUU0K05LZzlkVXQxaDdNb0pJa0NJcXArK2RjL3RlQ1gvMD0=",
|
||||
"rawQuality": "Deadpool (2016) 1080p UHD BluRay DV HDR 10bit HEVC [Hindi DDP 5.1 + English DDP 5.1] x265 (NMcT-UHDMovies [12GB]",
|
||||
"driveleechRedirectUrl": "javascript:void(0);"
|
||||
},
|
||||
{
|
||||
"quality": "1080p | BluRay",
|
||||
"size": "11GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2hBMkpOb2VJRHdGV1ROVTRVc2w5WGJERTNDVlhFUkE3TjdCUWdkcENqZXkwNXFlM1dRK0MwUjJucmpNTjJlUW5sRDZqWXlSamZRa3dHZThVbG9NelErbHA2VXp5eTBlbG1iY0g2NFNGZE1XL2lTSzlyR3VsTHd4YjVGQXF0NTBDSlpuTVhjVmpycGVKNEtGbmlJVVg2VQ==",
|
||||
"rawQuality": "Deadpool (2016) 1080p UHD BluRay [Hindi DDP 5.1 + English DDP 5.1] x264 (LoRD-UHDMovies) [11GB]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=NUVMaXZ0dFJJWXd0WlJaWUZyMUpGaDFiRjhRUEt1SXRUT1JPd29JR3NMWEs1ZnNydWl1bjYxeVBCaFp0ZG52dA=="
|
||||
},
|
||||
{
|
||||
"quality": "1080p | BluRay",
|
||||
"size": "21GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2pLSkd1UmpiYmdQRExHQnJZbS9UaEhmTUlYT09LQnI5QXIxR1ZGd3JzRy80SXdMbjB4OW9jR1R1S2llMkRTSk02SWdpdkJtSmVmZlVHZ3JBTHdyUkE2djg0VWdISXhiTXM3Q3NwVFg3R3E3UU5rVDJ0RDlUb290ckNuZDRWczdvVEgrSk40N3FpK3Q2UWE2TlVxa0M3ag==",
|
||||
"rawQuality": "Deadpool (2016) 1080p BluRay REMUX AVC [Hindi DDP 5.1 + English DDP 5.1] x264 (CC-UHDMovies) [21GB]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=a3lXeUFGT29ZaVFZZFRua2lqOFZ3ZGF2bFRidFNaTWFqSGVYUWgrajZmMXl5TVVKclZWeHJFc1c1eDFFV2s3eQ=="
|
||||
},
|
||||
{
|
||||
"quality": "1080p | BluRay",
|
||||
"size": "11.6GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2g5N3pSVmlGNnNibnMrdktudm9QWk9STklic3VrRFZBRE5PWXphOExvcUY0Z1BmRno1cFRKRXZIaHBhV3NIRjVYSWZPa0hwbnBJR1pxSEhGME52RllGcUxvdGx0ZVpqdkVyK3BUdmtlbUlJWE9LRVd2MUlQUFZhZnE5ZTErWUVtNTJnNnVKbE83UkpiTURrZ21UeHZzcQ==",
|
||||
"rawQuality": "Deadpool (2016) 1080p BluRay AVC [Hindi DDP 5.1 + English DTS 5.1] x264 (HDMaNiAcS-UHDMovies) [11.6GB]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=bEtkV1ZOemMvVkFMdEVFRGtzK1ladkVndWFBYXhWMWVQSWN1aS96S0kvY2w4R0hUWm1MS25RbHIwVVk3N0RNVg=="
|
||||
},
|
||||
{
|
||||
"quality": "1080p | BluRay",
|
||||
"size": "6GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2dYWHN4ODgzeXUrOURvQTVPbHhOVEZUWnJOZzRBdTd5UkhDTmV6bDIyNnFwUFd6VDBMaXdaVFh6U05ZeVl2U3lIMTE3ZjZOTWlEMVgxWFMvTVFHMzBoWHQvK0p1Y3p3UHRsREJjT0w4bVhtekduMnh1dWRKS0NMSTV4bXV6M3dtampiMXlLWDVpdVZLell1WC83b2tnLw==",
|
||||
"rawQuality": "Deadpool (2016) 1080p BluRay 10bit HEVC [Hindi DDP 5.1 + English AAC 5.1] x265 (BeiTai-UHDMovies) [6GB]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=OGlEbjNEN1JGVVBueGE5elVCWjY3bTdVYUpPdm5tZGMrTnNna0lsTWIwMzVSQThUbmdQOEE5WkVVbDVRZTFLUg=="
|
||||
},
|
||||
{
|
||||
"quality": "1080p | BluRay",
|
||||
"size": "2GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=bnpxOTF5Uk9VOVFZM2lEMkxXUmp0VTdoWDBEcGZMVWlrSmRYNlplRk9lQUdoYmJPbisvYi83VjZWNEdWbXVUai8vc3hQdGRwQ3lFRWtlZVVMR3YxSnR1MGV5dE1HTWs0bEtqM0x1bHhCT2dLSjBXTVNJWXVPK0FxeVZrUVZPUDhJMDJLcjRYOXZ4TVZXYlJ5dndpTDNsM0tmbkpQWVJ5em91NU41YndacERXSTNjemxCNE5GVnowM2F5QkYrVlE1SmhQVGcxelZIVjlNNXBONVJLYTcwK0dDNk42cWNMalE3Qk1WY0x1MVA1MFR0NkVGNGZkWkNRZnp6dS85Zml5Yw==",
|
||||
"rawQuality": "Deadpool (2016) 1080p BluRay 10bit HEVC [Hindi DDP 5.1 + English AAC 5.1] x265 (RARBG-UHDMovies) [2GB]",
|
||||
"driveleechRedirectUrl": "https://driveleech.net/r?key=UkVwSTIzU1gzQ3czMFBpQlBmeHB6NklzQjRRZFdMYStJaWI4UnZLdVd4QT0=&id=aVl6ZkFvQTZzUHdIUVE4SStONG02YmlONzkydzdVaUY0dDhIdHpxQVpjK2wrNmpUNkhCeHBwZWI3Q3Zwby9HLw=="
|
||||
},
|
||||
{
|
||||
"quality": "1080p | BluRay",
|
||||
"size": "2GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzbVhDeGlwbGZ5alNmd1dGSExtbllZNmlHQU1hd21Fd00xRGV5R081Ym1xSQ==",
|
||||
"rawQuality": "Deadpool (2016) 1080p BluRay 10bit HEVC [Hindi DDP 5.1 + English AAC 5.1] x265 (RARBG-UHDMovies) [2GB]",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.mov/1080p-60fps/"
|
||||
},
|
||||
{
|
||||
"quality": "1080p | BluRay",
|
||||
"size": "2GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzbTlTYS9SYXVlWmVCNUpiM3VLMGY5VFpGMUMwRnBhRllXMXFWeUE3Q3dGcA==",
|
||||
"rawQuality": "Deadpool (2016) 1080p BluRay 10bit HEVC [Hindi DDP 5.1 + English AAC 5.1] x265 (RARBG-UHDMovies) [2GB]",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.mov/1080p-10bit/"
|
||||
},
|
||||
{
|
||||
"quality": "1080p | BluRay",
|
||||
"size": "2GB",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzaEtZenp1aENhajVZeG5sTXNFcFVRTT0=",
|
||||
"rawQuality": "Deadpool (2016) 1080p BluRay 10bit HEVC [Hindi DDP 5.1 + English AAC 5.1] x265 (RARBG-UHDMovies) [2GB]",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.mov/4k-hdr/"
|
||||
},
|
||||
{
|
||||
"quality": "Unknown Quality",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzb3U1SmhRbk5yN0NDdGxPcmZNbUx4QkNPcEZ3bnQvM1R0Y1dsV2p1RFFGRQ==",
|
||||
"rawQuality": "Unknown Quality",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.mov/2160p-movies/"
|
||||
},
|
||||
{
|
||||
"quality": "Unknown Quality",
|
||||
"size": "Unknown",
|
||||
"link": "https://tech.unblockedgames.world/?sid=dmpORG1pUDNaSE4zMWt4RTFDVDMzdFBXM3RQUkFjRkF6T05icGNaL0FKejdzT0FXVGhwVVhCdVFkM294c2w4ag==",
|
||||
"rawQuality": "Unknown Quality",
|
||||
"driveleechRedirectUrl": "https://uhdmovies.mov/3d-movies/"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
@@ -1 +0,0 @@
|
||||
Unknown size
|
||||
+374
-337
File diff suppressed because it is too large
Load Diff
+88
-106
@@ -16,7 +16,7 @@ function processEpisode(episode, baseServer, serverName, serverNumber) {
|
||||
label: sub.label,
|
||||
url: `${baseServer}${sub.src}`
|
||||
})) : [];
|
||||
|
||||
|
||||
return {
|
||||
title: episode.show_title || episode.title,
|
||||
episode: episode.title,
|
||||
@@ -33,23 +33,23 @@ function processEpisode(episode, baseServer, serverName, serverNumber) {
|
||||
async function getMP4HydraStreams(tmdbId, mediaType, seasonNum = null, episodeNum = null) {
|
||||
try {
|
||||
console.log(`[MP4Hydra] Fetching streams for TMDB ID: ${tmdbId}, Type: ${mediaType}, Season: ${seasonNum}, Episode: ${episodeNum}`);
|
||||
|
||||
|
||||
// Get TMDB details
|
||||
const details = await getTMDBDetails(tmdbId, mediaType);
|
||||
if (!details) {
|
||||
console.log(`[MP4Hydra] Could not fetch details for TMDB ID: ${tmdbId}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
console.log(`[MP4Hydra] Found title: ${details.title} (${details.year})`);
|
||||
|
||||
|
||||
// Generate slug in the format "movie-name-year"
|
||||
let slug = details.slug;
|
||||
if (mediaType === 'movie' && details.year) {
|
||||
slug = `${details.slug}-${details.year}`;
|
||||
}
|
||||
console.log(`[MP4Hydra] Using slug: ${slug}`);
|
||||
|
||||
|
||||
// Create form data for multipart/form-data request
|
||||
const formData = new FormData();
|
||||
formData.append('v', '8');
|
||||
@@ -59,7 +59,7 @@ async function getMP4HydraStreams(tmdbId, mediaType, seasonNum = null, episodeNu
|
||||
se: seasonNum,
|
||||
ep: episodeNum
|
||||
}]));
|
||||
|
||||
|
||||
// Make request to MP4Hydra API
|
||||
const response = await axios({
|
||||
method: 'post',
|
||||
@@ -78,64 +78,61 @@ async function getMP4HydraStreams(tmdbId, mediaType, seasonNum = null, episodeNu
|
||||
},
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
|
||||
// Process response
|
||||
if (response.data && response.data.playlist && response.data.playlist.length > 0) {
|
||||
const playlist = response.data.playlist;
|
||||
const servers = response.data.servers;
|
||||
|
||||
|
||||
console.log(`[MP4Hydra] Found ${playlist.length} videos`);
|
||||
console.log(`[MP4Hydra] Available servers: ${Object.keys(servers).join(', ')}`);
|
||||
|
||||
|
||||
// For TV shows, find the specific episode
|
||||
if (mediaType === 'tv' && seasonNum && episodeNum) {
|
||||
const paddedSeason = seasonNum.toString().padStart(2, '0');
|
||||
const paddedEpisode = episodeNum.toString().padStart(2, '0');
|
||||
const seasonEpisode = `S${paddedSeason}E${paddedEpisode}`;
|
||||
|
||||
const targetEpisode = playlist.find(item =>
|
||||
|
||||
const targetEpisode = playlist.find(item =>
|
||||
item.title && item.title.toUpperCase() === seasonEpisode.toUpperCase()
|
||||
);
|
||||
|
||||
|
||||
if (!targetEpisode) {
|
||||
console.log(`[MP4Hydra] Could not find ${seasonEpisode}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
console.log(`[MP4Hydra] Found episode: ${targetEpisode.show_title || targetEpisode.title}`);
|
||||
|
||||
|
||||
// Process streams from main servers
|
||||
const streams = [];
|
||||
const serverConfig = [
|
||||
{ name: 'Beta', number: '#1' },
|
||||
{ name: 'Beta#3', number: '#2' }
|
||||
];
|
||||
|
||||
|
||||
serverConfig.forEach(server => {
|
||||
const serverName = server.name;
|
||||
const serverNumber = server.number;
|
||||
|
||||
|
||||
if (servers[serverName]) {
|
||||
const baseServer = servers[serverName];
|
||||
console.log(`[MP4Hydra] Processing server: ${serverName} (${baseServer})`);
|
||||
|
||||
|
||||
const processedEpisode = processEpisode(targetEpisode, baseServer, serverName, serverNumber);
|
||||
|
||||
|
||||
// Convert to standard stream format
|
||||
streams.push({
|
||||
title: `${details.title} - ${seasonEpisode} - ${processedEpisode.quality} [MP4Hydra ${serverNumber}]`,
|
||||
url: processedEpisode.videoUrl,
|
||||
quality: processedEpisode.quality,
|
||||
provider: "MP4Hydra",
|
||||
size: "Unknown size",
|
||||
behaviorHints: {
|
||||
notWebReady: true,
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
provider: "mp4hydra",
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Add subtitle tracks if available
|
||||
if (processedEpisode.subtitles && processedEpisode.subtitles.length > 0) {
|
||||
streams[streams.length - 1].subtitles = processedEpisode.subtitles.map(sub => ({
|
||||
@@ -145,43 +142,40 @@ async function getMP4HydraStreams(tmdbId, mediaType, seasonNum = null, episodeNu
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
|
||||
// For movies, process all videos
|
||||
const streams = [];
|
||||
const serverConfig = [
|
||||
{ name: 'Beta', number: '#1' },
|
||||
{ name: 'Beta#3', number: '#2' }
|
||||
];
|
||||
|
||||
|
||||
serverConfig.forEach(server => {
|
||||
const serverName = server.name;
|
||||
const serverNumber = server.number;
|
||||
|
||||
|
||||
if (servers[serverName]) {
|
||||
const baseServer = servers[serverName];
|
||||
console.log(`[MP4Hydra] Processing server: ${serverName} (${baseServer})`);
|
||||
|
||||
|
||||
playlist.forEach(item => {
|
||||
const processedItem = processEpisode(item, baseServer, serverName, serverNumber);
|
||||
|
||||
|
||||
// Convert to standard stream format
|
||||
streams.push({
|
||||
title: `${details.title} - ${processedItem.quality} [MP4Hydra ${serverNumber}]`,
|
||||
url: processedItem.videoUrl,
|
||||
quality: processedItem.quality,
|
||||
provider: "MP4Hydra",
|
||||
size: "Unknown size",
|
||||
behaviorHints: {
|
||||
notWebReady: true,
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
provider: "mp4hydra",
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Add subtitle tracks if available
|
||||
if (processedItem.subtitles && processedItem.subtitles.length > 0) {
|
||||
streams[streams.length - 1].subtitles = processedItem.subtitles.map(sub => ({
|
||||
@@ -192,19 +186,19 @@ async function getMP4HydraStreams(tmdbId, mediaType, seasonNum = null, episodeNu
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return streams;
|
||||
} else {
|
||||
// Try with original title if the first attempt failed
|
||||
if (details.title !== details.original_title) {
|
||||
return await tryAlternativeTitle(details, mediaType, seasonNum, episodeNum);
|
||||
}
|
||||
|
||||
|
||||
// Try without year for movies
|
||||
if (mediaType === 'movie' && details.year) {
|
||||
return await tryWithoutYear(details, mediaType, seasonNum, episodeNum);
|
||||
}
|
||||
|
||||
|
||||
console.log('[MP4Hydra] No streaming links found');
|
||||
return [];
|
||||
}
|
||||
@@ -220,11 +214,11 @@ async function tryAlternativeTitle(details, mediaType, seasonNum, episodeNum) {
|
||||
console.log(`[MP4Hydra] Retrying with original title: ${details.original_title}`);
|
||||
const originalSlug = generateSlug(details.original_title);
|
||||
let originalFullSlug = originalSlug;
|
||||
|
||||
|
||||
if (mediaType === 'movie' && details.year) {
|
||||
originalFullSlug = `${originalSlug}-${details.year}`;
|
||||
}
|
||||
|
||||
|
||||
// Create new form data with original title
|
||||
const formData = new FormData();
|
||||
formData.append('v', '8');
|
||||
@@ -234,7 +228,7 @@ async function tryAlternativeTitle(details, mediaType, seasonNum, episodeNum) {
|
||||
se: seasonNum,
|
||||
ep: episodeNum
|
||||
}]));
|
||||
|
||||
|
||||
const response = await axios({
|
||||
method: 'post',
|
||||
url: 'https://mp4hydra.org/info2?v=8',
|
||||
@@ -252,61 +246,58 @@ async function tryAlternativeTitle(details, mediaType, seasonNum, episodeNum) {
|
||||
},
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
|
||||
// Process response
|
||||
if (response.data && response.data.playlist && response.data.playlist.length > 0) {
|
||||
const playlist = response.data.playlist;
|
||||
const servers = response.data.servers;
|
||||
|
||||
|
||||
console.log(`[MP4Hydra] Found ${playlist.length} videos with original title`);
|
||||
|
||||
|
||||
// For TV shows, find the specific episode
|
||||
if (mediaType === 'tv' && seasonNum && episodeNum) {
|
||||
const paddedSeason = seasonNum.toString().padStart(2, '0');
|
||||
const paddedEpisode = episodeNum.toString().padStart(2, '0');
|
||||
const seasonEpisode = `S${paddedSeason}E${paddedEpisode}`;
|
||||
|
||||
const targetEpisode = playlist.find(item =>
|
||||
|
||||
const targetEpisode = playlist.find(item =>
|
||||
item.title && item.title.toUpperCase() === seasonEpisode.toUpperCase()
|
||||
);
|
||||
|
||||
|
||||
if (!targetEpisode) {
|
||||
console.log(`[MP4Hydra] Could not find ${seasonEpisode} with original title`);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
// Process streams from main servers
|
||||
const streams = [];
|
||||
const serverConfig = [
|
||||
{ name: 'Beta', number: '#1' },
|
||||
{ name: 'Beta#3', number: '#2' }
|
||||
];
|
||||
|
||||
|
||||
serverConfig.forEach(server => {
|
||||
const serverName = server.name;
|
||||
const serverNumber = server.number;
|
||||
|
||||
|
||||
if (servers[serverName]) {
|
||||
const baseServer = servers[serverName];
|
||||
console.log(`[MP4Hydra] Processing server: ${serverName} (${baseServer})`);
|
||||
|
||||
|
||||
const processedEpisode = processEpisode(targetEpisode, baseServer, serverName, serverNumber);
|
||||
|
||||
|
||||
// Convert to standard stream format
|
||||
streams.push({
|
||||
title: `${details.original_title} - ${seasonEpisode} - ${processedEpisode.quality} [MP4Hydra ${serverNumber}]`,
|
||||
url: processedEpisode.videoUrl,
|
||||
quality: processedEpisode.quality,
|
||||
provider: "MP4Hydra",
|
||||
size: "Unknown size",
|
||||
behaviorHints: {
|
||||
notWebReady: true,
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
provider: "mp4hydra",
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Add subtitle tracks if available
|
||||
if (processedEpisode.subtitles && processedEpisode.subtitles.length > 0) {
|
||||
streams[streams.length - 1].subtitles = processedEpisode.subtitles.map(sub => ({
|
||||
@@ -316,43 +307,36 @@ async function tryAlternativeTitle(details, mediaType, seasonNum, episodeNum) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
|
||||
// For movies, process all videos
|
||||
const streams = [];
|
||||
const serverConfig = [
|
||||
{ name: 'Beta', number: '#1' },
|
||||
{ name: 'Beta#3', number: '#2' }
|
||||
];
|
||||
|
||||
|
||||
serverConfig.forEach(server => {
|
||||
const serverName = server.name;
|
||||
const serverNumber = server.number;
|
||||
|
||||
|
||||
if (servers[serverName]) {
|
||||
const baseServer = servers[serverName];
|
||||
console.log(`[MP4Hydra] Processing server: ${serverName} (${baseServer})`);
|
||||
|
||||
|
||||
playlist.forEach(item => {
|
||||
const processedItem = processEpisode(item, baseServer, serverName, serverNumber);
|
||||
|
||||
|
||||
// Convert to standard stream format
|
||||
streams.push({
|
||||
name: `${details.original_title}`,
|
||||
title: `${details.original_title} - ${processedItem.quality} [MP4Hydra ${serverNumber}]`,
|
||||
url: processedItem.videoUrl,
|
||||
quality: processedItem.quality,
|
||||
provider: "MP4Hydra",
|
||||
size: "Unknown size",
|
||||
behaviorHints: {
|
||||
notWebReady: true,
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
}
|
||||
quality: processedItem.quality
|
||||
});
|
||||
|
||||
|
||||
// Add subtitle tracks if available
|
||||
if (processedItem.subtitles && processedItem.subtitles.length > 0) {
|
||||
streams[streams.length - 1].subtitles = processedItem.subtitles.map(sub => ({
|
||||
@@ -363,10 +347,10 @@ async function tryAlternativeTitle(details, mediaType, seasonNum, episodeNum) {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error(`[MP4Hydra] Error with alternative title:`, error.message);
|
||||
@@ -379,7 +363,7 @@ async function tryWithoutYear(details, mediaType, seasonNum, episodeNum) {
|
||||
try {
|
||||
console.log(`[MP4Hydra] Retrying with title only (without year): ${details.title}`);
|
||||
const titleOnlySlug = details.slug;
|
||||
|
||||
|
||||
// Create new form data with title only
|
||||
const formData = new FormData();
|
||||
formData.append('v', '8');
|
||||
@@ -389,7 +373,7 @@ async function tryWithoutYear(details, mediaType, seasonNum, episodeNum) {
|
||||
se: seasonNum,
|
||||
ep: episodeNum
|
||||
}]));
|
||||
|
||||
|
||||
const response = await axios({
|
||||
method: 'post',
|
||||
url: 'https://mp4hydra.org/info2?v=8',
|
||||
@@ -407,47 +391,45 @@ async function tryWithoutYear(details, mediaType, seasonNum, episodeNum) {
|
||||
},
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
|
||||
// Process response
|
||||
if (response.data && response.data.playlist && response.data.playlist.length > 0) {
|
||||
const playlist = response.data.playlist;
|
||||
const servers = response.data.servers;
|
||||
|
||||
|
||||
console.log(`[MP4Hydra] Found ${playlist.length} videos with title only`);
|
||||
|
||||
|
||||
// For movies, process all videos
|
||||
const streams = [];
|
||||
const serverConfig = [
|
||||
{ name: 'Beta', number: '#1' },
|
||||
{ name: 'Beta#3', number: '#2' }
|
||||
];
|
||||
|
||||
|
||||
serverConfig.forEach(server => {
|
||||
const serverName = server.name;
|
||||
const serverNumber = server.number;
|
||||
|
||||
|
||||
if (servers[serverName]) {
|
||||
const baseServer = servers[serverName];
|
||||
console.log(`[MP4Hydra] Processing server: ${serverName} (${baseServer})`);
|
||||
|
||||
|
||||
playlist.forEach(item => {
|
||||
const processedItem = processEpisode(item, baseServer, serverName, serverNumber);
|
||||
|
||||
|
||||
// Convert to standard stream format
|
||||
streams.push({
|
||||
name: `${details.title}`,
|
||||
title: `${details.title} - ${processedItem.quality} [MP4Hydra ${serverNumber}]`,
|
||||
url: processedItem.videoUrl,
|
||||
quality: processedItem.quality,
|
||||
provider: "MP4Hydra",
|
||||
size: "Unknown size",
|
||||
behaviorHints: {
|
||||
notWebReady: true,
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
provider: "mp4hydra",
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Add subtitle tracks if available
|
||||
if (processedItem.subtitles && processedItem.subtitles.length > 0) {
|
||||
streams[streams.length - 1].subtitles = processedItem.subtitles.map(sub => ({
|
||||
@@ -458,10 +440,10 @@ async function tryWithoutYear(details, mediaType, seasonNum, episodeNum) {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error(`[MP4Hydra] Error with title-only:`, error.message);
|
||||
@@ -473,15 +455,15 @@ async function tryWithoutYear(details, mediaType, seasonNum, episodeNum) {
|
||||
async function getTMDBDetails(tmdbId, mediaType) {
|
||||
const { getTmdbApiKey } = require('../utils/tmdbKey');
|
||||
const TMDB_API_KEY = getTmdbApiKey();
|
||||
if(!TMDB_API_KEY){
|
||||
if (!TMDB_API_KEY) {
|
||||
console.warn('[MP4Hydra] TMDB API key missing; skipping TMDB metadata fetch');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
console.log(`[MP4Hydra] Fetching ${mediaType} details for TMDB ID: ${tmdbId}`);
|
||||
const response = await axios.get(`https://api.themoviedb.org/3/${mediaType}/${tmdbId}?api_key=${TMDB_API_KEY}`);
|
||||
|
||||
const response = await axios.get(`https://api.themoviedb.org/3/${mediaType}/${tmdbId}?api_key=${TMDB_API_KEY}`);
|
||||
|
||||
if (mediaType === 'movie') {
|
||||
return {
|
||||
title: response.data.title,
|
||||
|
||||
+143
-187
@@ -86,7 +86,7 @@ const getCookieForRequest = async (regionPreference = null, userCookie = null) =
|
||||
console.log('[CookieManager] Using user-supplied cookie from global state (legacy mode) for this cycle.');
|
||||
baseCookieToUse = global.currentRequestUserCookie;
|
||||
}
|
||||
// No file-based fallback rotation (env-only)
|
||||
// No file-based fallback rotation (env-only)
|
||||
else {
|
||||
console.log('[CookieManager] No user-supplied cookies available for this cycle.');
|
||||
baseCookieToUse = '';
|
||||
@@ -156,9 +156,9 @@ const FEBBOX_FILE_SHARE_LIST_URL = "https://www.febbox.com/file/file_share_list"
|
||||
// const SCRAPER_API_URL = 'https://api.scraperapi.com/';
|
||||
// TMDB key now sourced from config/env; no hardcoded public key
|
||||
const { getTmdbApiKey } = require('../utils/tmdbKey');
|
||||
function obtainTmdbKey(){
|
||||
function obtainTmdbKey() {
|
||||
const k = getTmdbApiKey();
|
||||
if(!k) console.warn('[Showbox] No TMDB API key available (multi-key set empty).');
|
||||
if (!k) console.warn('[Showbox] No TMDB API key available (multi-key set empty).');
|
||||
return k;
|
||||
}
|
||||
const TMDB_BASE_URL = 'https://api.themoviedb.org/3';
|
||||
@@ -169,8 +169,7 @@ const CACHE_DIR = process.env.SHOWBOX_CACHE_DIR
|
||||
? process.env.SHOWBOX_CACHE_DIR
|
||||
: (process.env.VERCEL ? path.join('/tmp', '.cache') : path.join(require('os').tmpdir(), 'tmdb-embed-showbox-cache'));
|
||||
console.log(`Using cache directory: ${CACHE_DIR}`);
|
||||
// MODIFICATION: Remove hardcoded SHOWBOX_PROXY_URL, will use environment variable
|
||||
// const SHOWBOX_PROXY_URL = "https://starlit-valkyrie-39f5ab.netlify.app/?destination=";
|
||||
|
||||
|
||||
// Ensure cache directories exist
|
||||
const ensureCacheDir = async (dirPath) => {
|
||||
@@ -535,9 +534,9 @@ const _searchAndExtractShowboxUrl = async (searchTerm, originalTmdbTitle, mediaY
|
||||
if (cachedResult === 'NO_MATCH_FOUND') return { url: null, score: -1 };
|
||||
return { url: cachedResult, score: 10 };
|
||||
} else if (cachedResult.result === 'NO_MATCH_FOUND') {
|
||||
// For stream searches, always retry immediately - content availability can change anytime
|
||||
console.log(` CACHE HIT: Previously no match found for ${originalTmdbTitle} ${mediaYear || ''}, but retrying immediately for fresh content.`);
|
||||
// Continue to search logic below to retry immediately
|
||||
// For stream searches, always retry immediately - content availability can change anytime
|
||||
console.log(` CACHE HIT: Previously no match found for ${originalTmdbTitle} ${mediaYear || ''}, but retrying immediately for fresh content.`);
|
||||
// Continue to search logic below to retry immediately
|
||||
} else {
|
||||
console.log(` CACHE HIT for ShowBox search best match URL (${originalTmdbTitle} ${mediaYear || ''}): ${cachedResult}`);
|
||||
return { url: cachedResult, score: 10 };
|
||||
@@ -820,7 +819,7 @@ const _searchAndExtractShowboxUrl = async (searchTerm, originalTmdbTitle, mediaY
|
||||
// Allow for very close matches (like plurals or slight variations)
|
||||
if (tmdbWord.length > 3 && itemWord.length > 3) {
|
||||
return (itemWord.includes(tmdbWord) && itemWord.length - tmdbWord.length <= 2) ||
|
||||
(tmdbWord.includes(itemWord) && tmdbWord.length - itemWord.length <= 2);
|
||||
(tmdbWord.includes(itemWord) && tmdbWord.length - itemWord.length <= 2);
|
||||
}
|
||||
return false;
|
||||
})) {
|
||||
@@ -989,8 +988,8 @@ const getShowboxUrlFromTmdbInfo = async (tmdbType, tmdbId, regionPreference = nu
|
||||
let tmdbData = await getFromCache(mainCacheKey, mainCacheSubDir);
|
||||
|
||||
if (!tmdbData || process.env.DISABLE_CACHE === 'true') {
|
||||
const TMDB_API_KEY = obtainTmdbKey();
|
||||
const tmdbApiUrl = `${TMDB_BASE_URL}/${tmdbType}/${tmdbId}?api_key=${TMDB_API_KEY}`;
|
||||
const TMDB_API_KEY = obtainTmdbKey();
|
||||
const tmdbApiUrl = `${TMDB_BASE_URL}/${tmdbType}/${tmdbId}?api_key=${TMDB_API_KEY}`;
|
||||
console.log(` Fetching TMDB data from: ${tmdbApiUrl}`);
|
||||
try {
|
||||
const response = await axios.get(tmdbApiUrl, { timeout: 10000 });
|
||||
@@ -1022,8 +1021,8 @@ const getShowboxUrlFromTmdbInfo = async (tmdbType, tmdbId, regionPreference = nu
|
||||
let tmdbAlternativeTitlesData = await getFromCache(altTitlesCacheKey, mainCacheSubDir);
|
||||
|
||||
if (!tmdbAlternativeTitlesData || process.env.DISABLE_CACHE === 'true') {
|
||||
const TMDB_API_KEY = obtainTmdbKey();
|
||||
const altTitlesApiUrl = `${TMDB_BASE_URL}/${tmdbType}/${tmdbId}/alternative_titles?api_key=${TMDB_API_KEY}`;
|
||||
const TMDB_API_KEY = obtainTmdbKey();
|
||||
const altTitlesApiUrl = `${TMDB_BASE_URL}/${tmdbType}/${tmdbId}/alternative_titles?api_key=${TMDB_API_KEY}`;
|
||||
console.log(` Fetching TMDB alternative titles from: ${altTitlesApiUrl}`);
|
||||
try {
|
||||
const response = await axios.get(altTitlesApiUrl, { timeout: 10000 });
|
||||
@@ -1048,8 +1047,8 @@ const getShowboxUrlFromTmdbInfo = async (tmdbType, tmdbId, regionPreference = nu
|
||||
let tmdbBackdropPaths = [];
|
||||
|
||||
if (!tmdbImagesData || process.env.DISABLE_CACHE === 'true') {
|
||||
const TMDB_API_KEY = obtainTmdbKey();
|
||||
const imagesApiUrl = `${TMDB_BASE_URL}/${tmdbType}/${tmdbId}/images?api_key=${TMDB_API_KEY}`;
|
||||
const TMDB_API_KEY = obtainTmdbKey();
|
||||
const imagesApiUrl = `${TMDB_BASE_URL}/${tmdbType}/${tmdbId}/images?api_key=${TMDB_API_KEY}`;
|
||||
console.log(` Fetching TMDB images from: ${imagesApiUrl}`);
|
||||
try {
|
||||
const response = await axios.get(imagesApiUrl, { timeout: 10000 });
|
||||
@@ -1353,10 +1352,9 @@ class ShowBoxScraper {
|
||||
// Store the region preference and user cookie
|
||||
this.regionPreference = regionPreference;
|
||||
this.userCookie = userCookie;
|
||||
// ScraperAPI key support removed
|
||||
// ScraperAPI key support removed
|
||||
|
||||
|
||||
// Initialize proxy rotation counter for this instance
|
||||
this.proxyCounter = Math.floor(Math.random() * 1000); // Random start to avoid patterns
|
||||
|
||||
this.baseHeaders = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||
@@ -1374,34 +1372,10 @@ class ShowBoxScraper {
|
||||
};
|
||||
}
|
||||
|
||||
// Get the next proxy URL from the rotation
|
||||
getNextProxy() {
|
||||
// Read proxy configuration from environment
|
||||
const useRotatingProxy = process.env.SHOWBOX_USE_ROTATING_PROXY === 'true';
|
||||
const primaryProxy = process.env.SHOWBOX_PROXY_URL_VALUE;
|
||||
const alternateProxy = process.env.SHOWBOX_PROXY_URL_ALTERNATE;
|
||||
|
||||
// Increment the counter for this instance
|
||||
this.proxyCounter++;
|
||||
|
||||
// ScraperAPI support removed; simple proxy rotation only
|
||||
|
||||
// Return direct connection if both proxies are missing
|
||||
if (!primaryProxy && !alternateProxy) return null;
|
||||
|
||||
// If rotation disabled or alternate proxy not set, just use primary proxy
|
||||
if (!useRotatingProxy || !alternateProxy) return primaryProxy;
|
||||
|
||||
// Use modulo to alternate between available proxies
|
||||
const proxyIndex = this.proxyCounter % 2;
|
||||
const selectedProxy = proxyIndex === 0 ? primaryProxy : alternateProxy;
|
||||
|
||||
console.log(`[Rotating Proxy] Selected proxy ${proxyIndex + 1}/2 for request #${this.proxyCounter}`);
|
||||
return selectedProxy;
|
||||
}
|
||||
|
||||
async _makeRequest(url, isJsonExpected = false) {
|
||||
// Removed unused timerLabel variable (profiling optional)
|
||||
// Removed unused timerLabel variable (profiling optional)
|
||||
|
||||
// For JSON requests, we still cache normally since they're small
|
||||
if (isJsonExpected) {
|
||||
@@ -1416,24 +1390,10 @@ class ShowBoxScraper {
|
||||
}
|
||||
// For HTML requests, we don't cache the raw HTML anymore - it's too large and wasteful
|
||||
|
||||
// Get the next proxy URL from the rotation
|
||||
const selectedProxy = this.getNextProxy();
|
||||
let requestUrl = url;
|
||||
// removed unused isUsingScraperApi flag
|
||||
// Always make direct requests
|
||||
const requestUrl = url;
|
||||
|
||||
if (selectedProxy) {
|
||||
if (typeof selectedProxy === 'function') {
|
||||
requestUrl = selectedProxy(url); // Direct transformation
|
||||
} else if (selectedProxy.trim() !== '') {
|
||||
// This is a regular proxy URL
|
||||
requestUrl = `${selectedProxy}${encodeURIComponent(url)}`;
|
||||
console.log(`ShowBoxScraper: Making request to: ${url} via Proxy: ${selectedProxy}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`ShowBoxScraper: Making direct request to: ${url} (no proxy available)`);
|
||||
}
|
||||
|
||||
// Removed console.time call (timerLabel removed)
|
||||
// Removed console.time call (timerLabel removed)
|
||||
|
||||
// Get the cookie with region preference if site requires it
|
||||
let cookieValue = null;
|
||||
@@ -1463,7 +1423,7 @@ class ShowBoxScraper {
|
||||
try {
|
||||
const response = await axios.get(requestUrl, {
|
||||
headers: currentHeaders,
|
||||
timeout: 30000 // Consider increasing if proxy adds significant latency
|
||||
timeout: 30000
|
||||
});
|
||||
const responseData = response.data;
|
||||
|
||||
@@ -1847,7 +1807,7 @@ const convertImdbToTmdb = async (imdbId, regionPreference = null, expectedType =
|
||||
|
||||
if (findResults) {
|
||||
let result = null;
|
||||
|
||||
|
||||
// Context-aware prioritization based on expected type
|
||||
if (expectedType === 'tv' || expectedType === 'series') {
|
||||
// For series requests, prioritize TV results
|
||||
@@ -1875,7 +1835,7 @@ const convertImdbToTmdb = async (imdbId, regionPreference = null, expectedType =
|
||||
result = { tmdbId: String(findResults.tv_results[0].id), tmdbType: 'tv', title: findResults.tv_results[0].name || findResults.tv_results[0].original_name };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (findResults.person_results && findResults.person_results.length > 0 && !result) {
|
||||
// Could handle other types if necessary, e.g. person, but for streams, movie/tv are key
|
||||
console.log(` IMDb ID ${imdbId} resolved to a person, not a movie or TV show on TMDB.`);
|
||||
@@ -1908,47 +1868,47 @@ const getStreamsFromTmdbId = async (tmdbType, tmdbId, seasonNum = null, episodeN
|
||||
console.log(`Getting streams for TMDB ${tmdbType}/${tmdbId}${seasonNum !== null ? `, Season ${seasonNum}` : ''}${episodeNum !== null ? `, Episode ${episodeNum}` : ''}`);
|
||||
|
||||
// PRIMARY METHOD: Try PStream API first (if enabled)
|
||||
|
||||
try {
|
||||
// Get TMDB data to extract IMDB ID
|
||||
const tmdbData = await getTmdbDataForPStream(tmdbType, tmdbId);
|
||||
if (tmdbData && tmdbData.imdbId) {
|
||||
console.log(`[PStream Primary] Attempting PStream API for IMDB ID: ${tmdbData.imdbId}`);
|
||||
|
||||
const pstreamStreams = await getStreamsFromPStreamAPI(
|
||||
tmdbData.imdbId,
|
||||
tmdbType,
|
||||
seasonNum,
|
||||
episodeNum,
|
||||
regionPreference,
|
||||
userCookie
|
||||
);
|
||||
try {
|
||||
// Get TMDB data to extract IMDB ID
|
||||
const tmdbData = await getTmdbDataForPStream(tmdbType, tmdbId);
|
||||
if (tmdbData && tmdbData.imdbId) {
|
||||
console.log(`[PStream Primary] Attempting PStream API for IMDB ID: ${tmdbData.imdbId}`);
|
||||
|
||||
if (pstreamStreams && pstreamStreams.length > 0) {
|
||||
console.log(`[PStream Primary] SUCCESS: Found ${pstreamStreams.length} streams from PStream API`);
|
||||
const pstreamStreams = await getStreamsFromPStreamAPI(
|
||||
tmdbData.imdbId,
|
||||
tmdbType,
|
||||
seasonNum,
|
||||
episodeNum,
|
||||
regionPreference,
|
||||
userCookie
|
||||
);
|
||||
|
||||
// Fetch sizes for PStream streams
|
||||
if (pstreamStreams.length > 0) {
|
||||
const sizePromises = pstreamStreams.map(async (stream) => {
|
||||
stream.size = await fetchStreamSize(stream.url);
|
||||
return stream;
|
||||
});
|
||||
await Promise.all(sizePromises); // removed unused streamsWithSizes var
|
||||
}
|
||||
if (pstreamStreams && pstreamStreams.length > 0) {
|
||||
console.log(`[PStream Primary] SUCCESS: Found ${pstreamStreams.length} streams from PStream API`);
|
||||
|
||||
const sortedStreams = sortStreamsByQuality(pstreamStreams);
|
||||
console.timeEnd(mainTimerLabel);
|
||||
return sortedStreams;
|
||||
} else {
|
||||
console.log(`[PStream Primary] No streams found from PStream API, falling back to FebBox`);
|
||||
// Fetch sizes for PStream streams
|
||||
if (pstreamStreams.length > 0) {
|
||||
const sizePromises = pstreamStreams.map(async (stream) => {
|
||||
stream.size = await fetchStreamSize(stream.url);
|
||||
return stream;
|
||||
});
|
||||
await Promise.all(sizePromises); // removed unused streamsWithSizes var
|
||||
}
|
||||
|
||||
const sortedStreams = sortStreamsByQuality(pstreamStreams);
|
||||
console.timeEnd(mainTimerLabel);
|
||||
return sortedStreams;
|
||||
} else {
|
||||
console.log(`[PStream Primary] Could not get IMDB ID from TMDB data, falling back to FebBox`);
|
||||
console.log(`[PStream Primary] No streams found from PStream API, falling back to FebBox`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[PStream Primary] Error with PStream API: ${error.message}, falling back to FebBox`);
|
||||
} else {
|
||||
console.log(`[PStream Primary] Could not get IMDB ID from TMDB data, falling back to FebBox`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[PStream Primary] Error with PStream API: ${error.message}, falling back to FebBox`);
|
||||
}
|
||||
|
||||
// FALLBACK METHOD: Use original FebBox/ShowBox logic
|
||||
console.log(`[Fallback] Using FebBox/ShowBox method`);
|
||||
const tmdbInfo = await getShowboxUrlFromTmdbInfo(tmdbType, tmdbId, regionPreference);
|
||||
@@ -1964,19 +1924,19 @@ const getStreamsFromTmdbId = async (tmdbType, tmdbId, seasonNum = null, episodeN
|
||||
|
||||
// Check if we have cached FebBox share links to avoid redundant ShowBox requests
|
||||
let febboxShareInfos = tmdbInfo.febboxShareInfos;
|
||||
|
||||
|
||||
if (!febboxShareInfos) {
|
||||
// Only make ShowBox request if FebBox links are not cached
|
||||
console.log(`No cached FebBox links found, extracting from ShowBox: ${showboxUrl}`);
|
||||
const showboxScraper = new ShowBoxScraper(regionPreference, userCookie);
|
||||
const showboxScraper = new ShowBoxScraper(regionPreference, userCookie);
|
||||
febboxShareInfos = await showboxScraper.extractFebboxShareLinks(showboxUrl);
|
||||
|
||||
|
||||
if (!febboxShareInfos || febboxShareInfos.length === 0) {
|
||||
console.log(`No FebBox share links found for ${showboxUrl}`);
|
||||
console.timeEnd(mainTimerLabel);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
// Update cache with FebBox share links to avoid future ShowBox requests
|
||||
if (process.env.DISABLE_CACHE !== 'true') {
|
||||
const updatedTmdbInfo = { ...tmdbInfo, febboxShareInfos };
|
||||
@@ -2020,10 +1980,12 @@ const getStreamsFromTmdbId = async (tmdbType, tmdbId, seasonNum = null, episodeN
|
||||
}
|
||||
} catch (e) { /* ignore if URL parsing fails */ }
|
||||
streamsFromThisShareInfo.push({
|
||||
name: streamTitle,
|
||||
title: streamTitle,
|
||||
url: source.url,
|
||||
quality: parseQualityFromLabel(source.label),
|
||||
codecs: extractCodecDetails(key5FromDirectSource || streamTitle)
|
||||
provider: 'Febbox Direct',
|
||||
headers: {}
|
||||
});
|
||||
}
|
||||
// If direct sources are found, original code used 'continue',
|
||||
@@ -2047,10 +2009,12 @@ const getStreamsFromTmdbId = async (tmdbType, tmdbId, seasonNum = null, episodeN
|
||||
|
||||
const streamTitle = `${baseStreamTitle} - ${source.label}`;
|
||||
streamsFromThisShareInfo.push({
|
||||
name: streamTitle,
|
||||
title: streamTitle,
|
||||
url: source.url,
|
||||
quality: parseQualityFromLabel(source.label),
|
||||
codecs: extractCodecDetails(source.detailedFilename || streamTitle)
|
||||
provider: 'Febbox FID',
|
||||
headers: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2074,7 +2038,7 @@ const getStreamsFromTmdbId = async (tmdbType, tmdbId, seasonNum = null, episodeN
|
||||
stream.size = await fetchStreamSize(stream.url);
|
||||
return stream;
|
||||
});
|
||||
await Promise.all(sizePromises); // removed unused streamsWithSizes var
|
||||
await Promise.all(sizePromises); // removed unused streamsWithSizes var
|
||||
console.timeEnd(`getStreamsFromTmdbId_fetchStreamSizes_${tmdbType}_${tmdbId}`);
|
||||
}
|
||||
|
||||
@@ -2242,10 +2206,12 @@ const processShowWithSeasonsEpisodes = async (febboxUrl, showboxTitle, seasonNum
|
||||
for (const source of directSources) {
|
||||
const streamTitle = `${showboxTitle} - ${source.label}`;
|
||||
streamsForThisCall.push({ // MODIFIED: Push to streamsForThisCall
|
||||
name: streamTitle,
|
||||
title: streamTitle,
|
||||
url: source.url,
|
||||
quality: parseQualityFromLabel(source.label),
|
||||
codecs: extractCodecDetails(source.detailedFilename || streamTitle)
|
||||
provider: 'Febbox Direct',
|
||||
headers: {}
|
||||
});
|
||||
}
|
||||
console.timeEnd(processTimerLabel);
|
||||
@@ -2262,10 +2228,12 @@ const processShowWithSeasonsEpisodes = async (febboxUrl, showboxTitle, seasonNum
|
||||
for (const source of sources) {
|
||||
const streamTitle = `${showboxTitle} - ${source.label}`;
|
||||
streamsForThisCall.push({ // MODIFIED: Push to streamsForThisCall
|
||||
name: streamTitle,
|
||||
title: streamTitle,
|
||||
url: source.url,
|
||||
quality: parseQualityFromLabel(source.label),
|
||||
codecs: extractCodecDetails(source.detailedFilename || streamTitle)
|
||||
provider: 'Febbox FID',
|
||||
headers: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2546,10 +2514,12 @@ const processShowWithSeasonsEpisodes = async (febboxUrl, showboxTitle, seasonNum
|
||||
|
||||
const streamTitle = `${showboxTitle} - S${seasonNum}${episodeNum && selectedEpisode && fid === selectedEpisode.fid ? `E${episodeNum}` : (episodeDetail ? `E${episodeDetail.episodeNum}` : '')} - ${episodeName} - ${source.label}`;
|
||||
streamsForThisCall.push({ // MODIFIED: Push to streamsForThisCall
|
||||
name: streamTitle,
|
||||
title: streamTitle,
|
||||
url: source.url,
|
||||
quality: parseQualityFromLabel(source.label),
|
||||
codecs: extractCodecDetails(source.detailedFilename || streamTitle)
|
||||
provider: 'Febbox FID',
|
||||
headers: {}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -2762,27 +2732,27 @@ const PSTREAM_ACCEPT_HEADER = 'pstream.org';
|
||||
const PSTREAM_REGION_MAPPING = {
|
||||
// US Regions
|
||||
'USA7': 'East',
|
||||
'USA6': 'West',
|
||||
'USA6': 'West',
|
||||
'USA5': 'East',
|
||||
'USA4': 'West',
|
||||
'USA3': 'West',
|
||||
'USA2': 'West',
|
||||
'USA1': 'West',
|
||||
|
||||
|
||||
// European Regions
|
||||
'UK3': 'Europe',
|
||||
'FR1': 'Europe',
|
||||
'DE2': 'Europe',
|
||||
'EU1': 'Europe',
|
||||
'EU2': 'Europe',
|
||||
|
||||
|
||||
// Asian Regions
|
||||
'HK1': 'Asia',
|
||||
'IN1': 'Asia',
|
||||
'SZ': 'Asia',
|
||||
'ASIA1': 'Asia',
|
||||
'ASIA2': 'Asia',
|
||||
|
||||
|
||||
// Other Regions
|
||||
'CA1': 'East', // Canada maps to East (US East)
|
||||
'AU1': 'Asia' // Australia maps to Asia
|
||||
@@ -2793,12 +2763,12 @@ const mapRegionForPStream = (febboxRegion) => {
|
||||
if (!febboxRegion) {
|
||||
return PSTREAM_REGION_MAPPING[DEFAULT_OSS_REGION] || 'East';
|
||||
}
|
||||
|
||||
|
||||
const mappedRegion = PSTREAM_REGION_MAPPING[febboxRegion];
|
||||
if (mappedRegion) {
|
||||
return mappedRegion;
|
||||
}
|
||||
|
||||
|
||||
// Fallback to East if region not found
|
||||
console.log(`[PStream] Region ${febboxRegion} not found in mapping, using East as fallback`);
|
||||
return 'East';
|
||||
@@ -2808,7 +2778,7 @@ const mapRegionForPStream = (febboxRegion) => {
|
||||
const getStreamsFromPStreamAPI = async (imdbId, tmdbType, seasonNum = null, episodeNum = null, regionPreference = null, userCookie = null) => {
|
||||
const timerLabel = `getStreamsFromPStreamAPI_${imdbId}_${tmdbType}` + (seasonNum ? `_s${seasonNum}` : '') + (episodeNum ? `_e${episodeNum}` : '');
|
||||
console.time(timerLabel);
|
||||
|
||||
|
||||
try {
|
||||
// Build the PStream API URL
|
||||
let apiUrl;
|
||||
@@ -2820,12 +2790,12 @@ const getStreamsFromPStreamAPI = async (imdbId, tmdbType, seasonNum = null, epis
|
||||
console.log(`[PStream] Invalid parameters for ${tmdbType}: season=${seasonNum}, episode=${episodeNum}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
console.log(`[PStream] Making request to: ${apiUrl}`);
|
||||
|
||||
|
||||
// Get the region for PStream API
|
||||
const pstreamRegion = mapRegionForPStream(regionPreference || global.lastRequestedRegion?.used);
|
||||
|
||||
|
||||
// Prepare headers for PStream API
|
||||
const headers = {
|
||||
'accept': PSTREAM_ACCEPT_HEADER,
|
||||
@@ -2841,7 +2811,7 @@ const getStreamsFromPStreamAPI = async (imdbId, tmdbType, seasonNum = null, epis
|
||||
'sec-fetch-site': 'same-site',
|
||||
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
|
||||
};
|
||||
|
||||
|
||||
// Add ui-token if available
|
||||
const uiToken = await getUiTokenForPStream(userCookie);
|
||||
if (uiToken) {
|
||||
@@ -2850,38 +2820,30 @@ const getStreamsFromPStreamAPI = async (imdbId, tmdbType, seasonNum = null, epis
|
||||
} else {
|
||||
console.log(`[PStream] No ui-token available, making unauthenticated request`);
|
||||
}
|
||||
|
||||
// Check if proxy should be used
|
||||
const useProxy = process.env.SHOWBOX_PROXY_URL_VALUE && process.env.SHOWBOX_PROXY_URL_VALUE.trim() !== '';
|
||||
let finalApiUrl = apiUrl;
|
||||
|
||||
if (useProxy) {
|
||||
finalApiUrl = `${process.env.SHOWBOX_PROXY_URL_VALUE}${encodeURIComponent(apiUrl)}`;
|
||||
console.log(`[PStream] Using proxy for request: ${finalApiUrl}`);
|
||||
} else {
|
||||
console.log(`[PStream] Making direct request (no proxy configured)`);
|
||||
}
|
||||
|
||||
|
||||
// Always use direct API URL
|
||||
const finalApiUrl = apiUrl;
|
||||
|
||||
// Make the request
|
||||
const response = await axios.get(finalApiUrl, {
|
||||
headers: headers,
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
|
||||
if (!response.data) {
|
||||
console.log(`[PStream] No data received from API`);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
console.log(`[PStream] Response received, processing streams...`);
|
||||
|
||||
|
||||
// Process the PStream API response
|
||||
const streams = processPStreamResponse(response.data, tmdbType, seasonNum, episodeNum);
|
||||
|
||||
|
||||
console.timeEnd(timerLabel);
|
||||
console.log(`[PStream] Found ${streams.length} streams`);
|
||||
return streams;
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[PStream] Error fetching streams: ${error.message}`);
|
||||
if (error.response) {
|
||||
@@ -2895,90 +2857,84 @@ const getStreamsFromPStreamAPI = async (imdbId, tmdbType, seasonNum = null, epis
|
||||
// Function to process PStream API response and convert to standard stream format
|
||||
const processPStreamResponse = (apiResponse, tmdbType, seasonNum, episodeNum) => {
|
||||
const streams = [];
|
||||
|
||||
|
||||
try {
|
||||
// Log the actual response structure for debugging
|
||||
console.log(`[PStream] API Response structure:`, JSON.stringify(apiResponse, null, 2).substring(0, 1000));
|
||||
console.log(`[PStream] API Response type:`, typeof apiResponse);
|
||||
console.log(`[PStream] API Response keys:`, apiResponse ? Object.keys(apiResponse) : 'null/undefined');
|
||||
|
||||
|
||||
// Check if response has streams object (PStream API format)
|
||||
if (apiResponse && apiResponse.streams && typeof apiResponse.streams === 'object') {
|
||||
console.log(`[PStream] Found streams object with ${Object.keys(apiResponse.streams).length} quality levels`);
|
||||
|
||||
|
||||
// Process streams object format
|
||||
for (const [quality, url] of Object.entries(apiResponse.streams)) {
|
||||
if (url && typeof url === 'string') {
|
||||
// Use "Auto" for name and actual filename for title
|
||||
const streamName = "Auto";
|
||||
const streamTitle = apiResponse.name || `${tmdbType === 'movie' ? 'PStream' : `PStream - S${seasonNum}E${episodeNum}`} - ${quality}`;
|
||||
|
||||
|
||||
streams.push({
|
||||
name: streamName,
|
||||
name: streamTitle,
|
||||
title: streamTitle,
|
||||
url: url,
|
||||
quality: parseQualityFromLabel(quality),
|
||||
codecs: extractCodecDetails(apiResponse.name || quality), // Use filename for codec detection
|
||||
size: apiResponse.size || 'Unknown size',
|
||||
provider: 'PStream'
|
||||
provider: 'PStream',
|
||||
headers: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(`[PStream] Successfully parsed ${streams.length} streams from streams object`);
|
||||
return streams;
|
||||
}
|
||||
|
||||
|
||||
// Check if response has sources array (alternative format)
|
||||
if (apiResponse && Array.isArray(apiResponse.sources)) {
|
||||
console.log(`[PStream] Found sources array with ${apiResponse.sources.length} sources`);
|
||||
|
||||
|
||||
for (const source of apiResponse.sources) {
|
||||
if (!source.url || !source.quality) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const streamName = "Auto";
|
||||
|
||||
const streamTitle = apiResponse.name || `${tmdbType === 'movie' ? 'PStream' : `PStream - S${seasonNum}E${episodeNum}`} - ${source.quality}`;
|
||||
|
||||
|
||||
streams.push({
|
||||
name: streamName,
|
||||
name: streamTitle,
|
||||
title: streamTitle,
|
||||
url: source.url,
|
||||
quality: parseQualityFromLabel(source.quality),
|
||||
codecs: source.codec ? [source.codec] : [],
|
||||
size: source.size || 'Unknown size',
|
||||
provider: 'PStream'
|
||||
provider: 'PStream',
|
||||
headers: {}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
|
||||
// Check if response has data array (another alternative format)
|
||||
if (apiResponse && Array.isArray(apiResponse.data)) {
|
||||
console.log(`[PStream] Found 'data' array instead of 'sources'`);
|
||||
|
||||
|
||||
for (const item of apiResponse.data) {
|
||||
if (item.url || item.file) {
|
||||
const streamName = "Auto";
|
||||
const streamTitle = apiResponse.name || `${tmdbType === 'movie' ? 'PStream' : `PStream - S${seasonNum}E${episodeNum}`} - ${item.quality || item.label || 'Unknown'}`;
|
||||
|
||||
|
||||
streams.push({
|
||||
name: streamName,
|
||||
name: streamTitle,
|
||||
title: streamTitle,
|
||||
url: item.url || item.file,
|
||||
quality: parseQualityFromLabel(item.quality || item.label),
|
||||
codecs: item.codec ? [item.codec] : [],
|
||||
size: item.size || 'Unknown size',
|
||||
provider: 'PStream'
|
||||
provider: 'PStream',
|
||||
headers: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
|
||||
// No recognized format found
|
||||
console.log(`[PStream] No recognized stream format found in response`);
|
||||
if (apiResponse && apiResponse.message) {
|
||||
@@ -2986,13 +2942,13 @@ const processPStreamResponse = (apiResponse, tmdbType, seasonNum, episodeNum) =>
|
||||
} else if (apiResponse && apiResponse.error) {
|
||||
console.log(`[PStream] API returned error:`, apiResponse.error);
|
||||
}
|
||||
|
||||
|
||||
return streams;
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[PStream] Error processing API response: ${error.message}`);
|
||||
}
|
||||
|
||||
|
||||
return streams;
|
||||
};
|
||||
|
||||
@@ -3000,15 +2956,15 @@ const processPStreamResponse = (apiResponse, tmdbType, seasonNum, episodeNum) =>
|
||||
const getTmdbDataForPStream = async (tmdbType, tmdbId) => {
|
||||
const cacheSubDir = 'tmdb_api';
|
||||
const cacheKey = `tmdb-${tmdbType}-${tmdbId}.json`;
|
||||
|
||||
|
||||
// Try cache first
|
||||
let tmdbData = await getFromCache(cacheKey, cacheSubDir);
|
||||
|
||||
|
||||
if (!tmdbData || process.env.DISABLE_CACHE === 'true') {
|
||||
const TMDB_API_KEY = obtainTmdbKey();
|
||||
const tmdbApiUrl = `${TMDB_BASE_URL}/${tmdbType}/${tmdbId}?api_key=${TMDB_API_KEY}`;
|
||||
const TMDB_API_KEY = obtainTmdbKey();
|
||||
const tmdbApiUrl = `${TMDB_BASE_URL}/${tmdbType}/${tmdbId}?api_key=${TMDB_API_KEY}`;
|
||||
console.log(` [PStream] Fetching TMDB data from: ${tmdbApiUrl}`);
|
||||
|
||||
|
||||
try {
|
||||
const response = await axios.get(tmdbApiUrl, { timeout: 10000 });
|
||||
tmdbData = response.data;
|
||||
@@ -3022,22 +2978,22 @@ const getTmdbDataForPStream = async (tmdbType, tmdbId) => {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!tmdbData) {
|
||||
console.log(` [PStream] No TMDB data found for ${tmdbType}/${tmdbId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Extract IMDB ID
|
||||
let imdbId = null;
|
||||
const externalIdsCacheKey = `tmdb-${tmdbType}-${tmdbId}-external_ids.json`;
|
||||
let externalIdsData = await getFromCache(externalIdsCacheKey, cacheSubDir);
|
||||
|
||||
|
||||
if (!externalIdsData || process.env.DISABLE_CACHE === 'true') {
|
||||
const TMDB_API_KEY = obtainTmdbKey();
|
||||
const externalIdsApiUrl = `${TMDB_BASE_URL}/${tmdbType}/${tmdbId}/external_ids?api_key=${TMDB_API_KEY}`;
|
||||
const TMDB_API_KEY = obtainTmdbKey();
|
||||
const externalIdsApiUrl = `${TMDB_BASE_URL}/${tmdbType}/${tmdbId}/external_ids?api_key=${TMDB_API_KEY}`;
|
||||
console.log(` [PStream] Fetching TMDB external IDs from: ${externalIdsApiUrl}`);
|
||||
|
||||
|
||||
try {
|
||||
const response = await axios.get(externalIdsApiUrl, { timeout: 10000 });
|
||||
externalIdsData = response.data;
|
||||
@@ -3050,21 +3006,21 @@ const getTmdbDataForPStream = async (tmdbType, tmdbId) => {
|
||||
console.log(` [PStream] Error fetching TMDB external IDs: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (externalIdsData && externalIdsData.imdb_id) {
|
||||
imdbId = externalIdsData.imdb_id;
|
||||
}
|
||||
|
||||
|
||||
if (!imdbId) {
|
||||
console.log(` [PStream] No IMDB ID found for ${tmdbType}/${tmdbId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
imdbId: imdbId,
|
||||
title: tmdbType === 'movie' ? tmdbData.title : tmdbData.name,
|
||||
year: tmdbData.release_date ? new Date(tmdbData.release_date).getFullYear() :
|
||||
tmdbData.first_air_date ? new Date(tmdbData.first_air_date).getFullYear() : null
|
||||
year: tmdbData.release_date ? new Date(tmdbData.release_date).getFullYear() :
|
||||
tmdbData.first_air_date ? new Date(tmdbData.first_air_date).getFullYear() : null
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3075,20 +3031,20 @@ const getUiTokenForPStream = async (userCookie = null) => {
|
||||
console.log('[PStream CookieManager] Using user-provided JWT token as ui-token');
|
||||
return userCookie;
|
||||
}
|
||||
|
||||
|
||||
// If user provided a regular cookie, try to convert it to JWT format
|
||||
if (userCookie && !userCookie.startsWith('eyJ')) {
|
||||
console.log('[PStream CookieManager] Regular cookie provided, attempting to use as ui-token');
|
||||
// For now, pass it through as-is - the API might handle conversion
|
||||
return userCookie;
|
||||
}
|
||||
|
||||
|
||||
// Check global cookie
|
||||
if (global.currentRequestUserCookie && global.currentRequestUserCookie.startsWith('eyJ')) {
|
||||
console.log('[PStream CookieManager] Using JWT token from global state');
|
||||
return global.currentRequestUserCookie;
|
||||
}
|
||||
|
||||
|
||||
// Try intelligent selection from cookies array if available
|
||||
if (global.currentRequestConfig && Array.isArray(global.currentRequestConfig.cookies) && global.currentRequestConfig.cookies.length > 0) {
|
||||
try {
|
||||
@@ -3118,7 +3074,7 @@ const getUiTokenForPStream = async (userCookie = null) => {
|
||||
console.warn(`[PStream CookieManager] Multi-cookie selection failed: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('[PStream CookieManager] No valid ui-token available');
|
||||
return null;
|
||||
};
|
||||
|
||||
+6
-15
@@ -35,16 +35,11 @@ const getVidZeeStreams = async (tmdbId, mediaType, seasonNum, episodeNum) => {
|
||||
|
||||
let finalApiUrl;
|
||||
const headers = {
|
||||
'Referer': `https://player.vidzee.wtf/embed/movie/${tmdbId}` // Default for proxy method
|
||||
'Referer': `https://player.vidzee.wtf/embed/movie/${tmdbId}`
|
||||
};
|
||||
const timeout = 7000; // Reduced timeout
|
||||
|
||||
const proxyBaseUrl = process.env.VIDZEE_PROXY_URL || process.env.SHOWBOX_PROXY_URL_VALUE;
|
||||
if (proxyBaseUrl) {
|
||||
finalApiUrl = proxyBaseUrl + encodeURIComponent(targetApiUrl);
|
||||
} else {
|
||||
finalApiUrl = targetApiUrl;
|
||||
}
|
||||
finalApiUrl = targetApiUrl;
|
||||
|
||||
console.log(`[VidZee] Fetching from server ${sr}: ${targetApiUrl}`);
|
||||
|
||||
@@ -89,17 +84,13 @@ const getVidZeeStreams = async (tmdbId, mediaType, seasonNum, episodeNum) => {
|
||||
const language = sourceItem.language || sourceItem.lang;
|
||||
|
||||
return {
|
||||
title: `VidZee S${sr} - ${quality}`,
|
||||
name: `VidZee Server${sr} - ${quality} - ${language}`,
|
||||
title: `VidZee Server${sr} - ${quality} - ${language}`,
|
||||
url: sourceItem.link, // Use sourceItem.link for the URL
|
||||
quality: quality,
|
||||
language: language,
|
||||
provider: "VidZee",
|
||||
size: "Unknown size",
|
||||
behaviorHints: {
|
||||
notWebReady: true,
|
||||
headers: {
|
||||
'Referer': 'https://core.vidzee.wtf/'
|
||||
}
|
||||
headers: {
|
||||
'Referer': 'https://core.vidzee.wtf/'
|
||||
}
|
||||
};
|
||||
}).filter(stream => stream.url);
|
||||
|
||||
@@ -0,0 +1,629 @@
|
||||
// MoviesClub Provider
|
||||
// Extracts streams from moviesapi.club
|
||||
|
||||
const axios = require('axios');
|
||||
const JsUnpacker = require('../utils/jsunpack');
|
||||
|
||||
async function getMoviesClubStreams(tmdbId, mediaType, season, episode) {
|
||||
console.log(`[moviesclub] Fetching streams for ${mediaType} tmdbId=${tmdbId} season=${season} episode=${episode}`);
|
||||
|
||||
const streams = [];
|
||||
|
||||
try {
|
||||
// Build the URL based on media type
|
||||
let url;
|
||||
if (mediaType === 'movie') {
|
||||
url = `https://moviesapi.club/movie/${tmdbId}`;
|
||||
} else if (mediaType === 'tv' && season && episode) {
|
||||
url = `https://moviesapi.club/tv/${tmdbId}-${season}-${episode}`;
|
||||
} else {
|
||||
console.log('[moviesclub] Invalid parameters for TV show');
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log(`[moviesclub] Fetching from: ${url}`);
|
||||
|
||||
// Fetch the main page
|
||||
const response = await axios.get(url, {
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'Connection': 'keep-alive',
|
||||
'Upgrade-Insecure-Requests': '1'
|
||||
}
|
||||
});
|
||||
|
||||
const html = response.data;
|
||||
console.log(`[moviesclub] Fetched ${html.length} characters`);
|
||||
|
||||
// Extract data-url from srvswitch-menu
|
||||
const dataUrlMatch = html.match(/data-url="([^"]+)"/);
|
||||
if (!dataUrlMatch) {
|
||||
console.log('[moviesclub] No data-url found in HTML');
|
||||
return [];
|
||||
}
|
||||
|
||||
const embedUrl = dataUrlMatch[1];
|
||||
console.log(`[moviesclub] Found embed URL: ${embedUrl}`);
|
||||
|
||||
// Check if this is a cdn.moviesapi.club embed URL with multiple servers
|
||||
if (embedUrl.includes('cdn.moviesapi.club')) {
|
||||
console.log('[moviesclub] Detected cdn.moviesapi.club embed URL, handling multiple servers');
|
||||
return await handleMultipleServers(embedUrl, mediaType, season, episode, tmdbId);
|
||||
}
|
||||
|
||||
// Handle regular embed URLs (existing logic)
|
||||
return await handleSingleServer(embedUrl, mediaType, season, episode, tmdbId);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[moviesclub] Error:`, error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle cdn.moviesapi.club URLs with multiple servers
|
||||
async function handleMultipleServers(embedUrl, mediaType, season, episode, tmdbId) {
|
||||
console.log(`[moviesclub] Handling multiple servers from: ${embedUrl}`);
|
||||
|
||||
const streams = [];
|
||||
|
||||
try {
|
||||
// Fetch the embed page
|
||||
const embedResponse = await axios.get(embedUrl, {
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'Connection': 'keep-alive'
|
||||
}
|
||||
});
|
||||
|
||||
const embedHtml = embedResponse.data;
|
||||
console.log(`[moviesclub] Fetched embed page: ${embedHtml.length} characters`);
|
||||
|
||||
// Extract server options from the HTML
|
||||
const serverMatches = embedHtml.match(/<div class="server"[^>]*data-hash="([^"]+)"[^>]*>([^<]+)<\/div>/gi);
|
||||
|
||||
if (!serverMatches || serverMatches.length === 0) {
|
||||
console.log('[moviesclub] No server options found, trying direct iframe extraction');
|
||||
// Try to extract the main iframe src
|
||||
const iframeMatch = embedHtml.match(/<iframe[^>]*src="([^"]*)"[^>]*>/i);
|
||||
if (iframeMatch) {
|
||||
const iframeSrc = iframeMatch[1];
|
||||
console.log(`[moviesclub] Found iframe src: ${iframeSrc}`);
|
||||
|
||||
// Try to decode if it's base64
|
||||
try {
|
||||
const decodedSrc = Buffer.from(iframeSrc.split('/').pop(), 'base64').toString('utf-8');
|
||||
console.log(`[moviesclub] Decoded iframe src: ${decodedSrc}`);
|
||||
|
||||
// If it's a URL, use it directly
|
||||
if (decodedSrc.startsWith('http')) {
|
||||
const stream = {
|
||||
name: `MoviesClub - Direct Stream`,
|
||||
title: `${mediaType === 'movie' ? 'Movie' : `S${season.toString().padStart(2, '0')}E${episode.toString().padStart(2, '0')}`} ${tmdbId} - MoviesClub Direct`,
|
||||
url: decodedSrc,
|
||||
quality: extractQualityFromUrl(decodedSrc) || '720p'
|
||||
};
|
||||
streams.push(stream);
|
||||
console.log(`[moviesclub] Added direct stream from iframe`);
|
||||
return streams;
|
||||
}
|
||||
} catch (decodeError) {
|
||||
console.log(`[moviesclub] Could not decode iframe src: ${decodeError.message}`);
|
||||
}
|
||||
}
|
||||
return streams;
|
||||
}
|
||||
|
||||
console.log(`[moviesclub] Found ${serverMatches.length} server options`);
|
||||
|
||||
// Process each server
|
||||
for (let i = 0; i < serverMatches.length; i++) {
|
||||
const serverMatch = serverMatches[i];
|
||||
const hashMatch = serverMatch.match(/data-hash="([^"]+)"/);
|
||||
const nameMatch = serverMatch.match(/>([^<]+)<\/div>/);
|
||||
|
||||
if (!hashMatch || !nameMatch) {
|
||||
console.log(`[moviesclub] Could not parse server ${i + 1}, skipping`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const encodedHash = hashMatch[1];
|
||||
const serverName = nameMatch[1].trim();
|
||||
|
||||
console.log(`[moviesclub] Processing server ${i + 1}: ${serverName}`);
|
||||
|
||||
try {
|
||||
// Construct the rcp URL from the hash
|
||||
const rcpUrl = `https://cloudnestra.com/rcp/${encodedHash}`;
|
||||
console.log(`[moviesclub] RCP URL: ${rcpUrl}`);
|
||||
|
||||
// Fetch the rcp URL to get the JavaScript with prorcp URL
|
||||
const rcpResponse = await axios.get(rcpUrl, {
|
||||
timeout: 8000,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'Connection': 'keep-alive',
|
||||
'Referer': embedUrl
|
||||
}
|
||||
});
|
||||
|
||||
const rcpHtml = rcpResponse.data;
|
||||
console.log(`[moviesclub] RCP response length: ${rcpHtml.length}`);
|
||||
|
||||
// Extract prorcp URL from the JavaScript
|
||||
const prorcpMatch = rcpHtml.match(/src:\s*['"]([^'"]*prorcp[^'"]*)['"]/i);
|
||||
if (!prorcpMatch) {
|
||||
// Try alternative patterns
|
||||
const altMatch = rcpHtml.match(/['"](\/prorcp\/[^'"]*)['"]/i);
|
||||
if (altMatch) {
|
||||
prorcpUrl = altMatch[1];
|
||||
} else {
|
||||
console.log(`[moviesclub] No prorcp URL found in rcp response`);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
prorcpUrl = prorcpMatch[1];
|
||||
}
|
||||
|
||||
console.log(`[moviesclub] Found prorcp URL: ${prorcpUrl}`);
|
||||
|
||||
// If it's a relative URL, make it absolute
|
||||
if (prorcpUrl.startsWith('/')) {
|
||||
prorcpUrl = `https://cloudnestra.com${prorcpUrl}`;
|
||||
console.log(`[moviesclub] Converted to absolute URL: ${prorcpUrl}`);
|
||||
}
|
||||
|
||||
// Fetch the prorcp URL to get the player HTML
|
||||
const prorcpResponse = await axios.get(prorcpUrl, {
|
||||
timeout: 8000,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'Connection': 'keep-alive',
|
||||
'Referer': rcpUrl
|
||||
}
|
||||
});
|
||||
|
||||
const prorcpHtml = prorcpResponse.data;
|
||||
console.log(`[moviesclub] Prorcp response length: ${prorcpHtml.length}`);
|
||||
|
||||
// Extract stream URL from Playerjs configuration
|
||||
const streamUrl = extractStreamFromPlayerjsUpdated(prorcpHtml);
|
||||
if (!streamUrl) {
|
||||
console.log(`[moviesclub] No stream URL found in prorcp response`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`[moviesclub] Extracted stream URL: ${streamUrl.substring(0, 100)}...`);
|
||||
|
||||
// Create stream object
|
||||
const stream = {
|
||||
name: `MoviesClub - ${serverName}`,
|
||||
title: `${mediaType === 'movie' ? 'Movie' : `S${season.toString().padStart(2, '0')}E${episode.toString().padStart(2, '0')}`} ${tmdbId} - ${serverName}`,
|
||||
url: streamUrl,
|
||||
quality: extractQualityFromUrl(streamUrl) || '720p',
|
||||
headers: {}
|
||||
};
|
||||
|
||||
streams.push(stream);
|
||||
console.log(`[moviesclub] Added stream from server: ${serverName}`);
|
||||
|
||||
} catch (serverError) {
|
||||
console.error(`[moviesclub] Error processing server ${serverName}:`, serverError.message);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[moviesclub] Total streams found: ${streams.length}`);
|
||||
return streams;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[moviesclub] Error in handleMultipleServers:`, error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle regular embed URLs (existing logic)
|
||||
async function handleSingleServer(embedUrl, mediaType, season, episode, tmdbId) {
|
||||
console.log(`[moviesclub] Handling single server from: ${embedUrl}`);
|
||||
|
||||
const streams = [];
|
||||
|
||||
try {
|
||||
// Fetch the embed page
|
||||
const embedResponse = await axios.get(embedUrl, {
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'Connection': 'keep-alive'
|
||||
}
|
||||
});
|
||||
|
||||
const embedHtml = embedResponse.data;
|
||||
console.log(`[moviesclub] Fetched embed page: ${embedHtml.length} characters`);
|
||||
|
||||
// Look for packed JavaScript using JsUnpacker.detect()
|
||||
const scriptTags = embedHtml.match(/<script[^>]*>[\s\S]*?<\/script>/gi) || [];
|
||||
let packedCode = null;
|
||||
|
||||
for (const scriptTag of scriptTags) {
|
||||
// Extract script content
|
||||
const scriptContent = scriptTag.replace(/<\/?script[^>]*>/gi, '').trim();
|
||||
|
||||
if (scriptContent) {
|
||||
// Create unpacker instance and check if it's packed
|
||||
const unpacker = new JsUnpacker(scriptContent);
|
||||
|
||||
if (unpacker.detect()) {
|
||||
console.log(`[moviesclub] Found packed JavaScript in script tag, length: ${scriptContent.length}`);
|
||||
packedCode = scriptContent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!packedCode) {
|
||||
console.log('[moviesclub] No packed JavaScript found');
|
||||
return [];
|
||||
}
|
||||
|
||||
// Unpack using JsUnpacker
|
||||
const unpacker = new JsUnpacker(packedCode);
|
||||
const unpackedCode = unpacker.unpack();
|
||||
|
||||
if (!unpackedCode) {
|
||||
console.log('[moviesclub] Failed to unpack JavaScript');
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log(`[moviesclub] Successfully unpacked code, length: ${unpackedCode.length}`);
|
||||
|
||||
// Parse the player configuration from unpacked code
|
||||
const playerConfig = parsePlayerConfig(unpackedCode);
|
||||
if (!playerConfig) {
|
||||
console.log('[moviesclub] Failed to parse player configuration');
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log('[moviesclub] Parsed player config:', playerConfig);
|
||||
|
||||
// Extract streams from the configuration
|
||||
if (playerConfig.file) {
|
||||
const stream = {
|
||||
name: `MoviesClub - ${playerConfig.title || 'Server 1'}`,
|
||||
title: playerConfig.title || `${mediaType === 'movie' ? 'Movie' : `S${season.toString().padStart(2, '0')}E${episode.toString().padStart(2, '0')}`} ${tmdbId} - MoviesClub`,
|
||||
url: playerConfig.file,
|
||||
quality: extractQualityFromUrl(playerConfig.file) || '720p',
|
||||
headers: {}
|
||||
};
|
||||
|
||||
// Add poster if available
|
||||
if (playerConfig.poster) {
|
||||
stream.poster = playerConfig.poster;
|
||||
}
|
||||
|
||||
// Add subtitles if available
|
||||
if (playerConfig.subtitle) {
|
||||
stream.subtitles = parseSubtitles(playerConfig.subtitle);
|
||||
}
|
||||
|
||||
streams.push(stream);
|
||||
}
|
||||
|
||||
console.log(`[moviesclub] Found ${streams.length} streams`);
|
||||
return streams;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[moviesclub] Error in handleSingleServer:`, error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Extract stream URL from Playerjs configuration in HTML
|
||||
function extractStreamFromPlayerjs(html) {
|
||||
try {
|
||||
// Look for the Playerjs constructor call
|
||||
const playerMatch = html.match(/new Playerjs\(\s*(\{[^}]*\})\s*\)/);
|
||||
if (!playerMatch) {
|
||||
console.log('[moviesclub] No Playerjs constructor found in HTML');
|
||||
return null;
|
||||
}
|
||||
|
||||
const configStr = playerMatch[1];
|
||||
console.log(`[moviesclub] Found Playerjs config: ${configStr.substring(0, 200)}...`);
|
||||
|
||||
// Extract the file URL from the configuration
|
||||
const fileMatch = configStr.match(/file:\s*['"]([^'"]+)['"]/);
|
||||
if (!fileMatch) {
|
||||
console.log('[moviesclub] No file URL found in Playerjs config');
|
||||
return null;
|
||||
}
|
||||
|
||||
const streamUrl = fileMatch[1];
|
||||
console.log(`[moviesclub] Extracted stream URL from Playerjs: ${streamUrl.substring(0, 100)}...`);
|
||||
|
||||
return streamUrl;
|
||||
|
||||
} catch (error) {
|
||||
console.error('[moviesclub] Error extracting stream from Playerjs:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract streams from server response
|
||||
function extractStreamsFromServer(serverData, serverName, mediaType, season, episode, tmdbId) {
|
||||
const streams = [];
|
||||
|
||||
try {
|
||||
// Handle different response formats
|
||||
if (typeof serverData === 'string') {
|
||||
// Try to parse as JSON
|
||||
try {
|
||||
serverData = JSON.parse(serverData);
|
||||
} catch (parseError) {
|
||||
console.log(`[moviesclub] Server response is not JSON, treating as direct URL: ${serverData.substring(0, 100)}`);
|
||||
// Treat as direct stream URL
|
||||
if (serverData.startsWith('http')) {
|
||||
const stream = {
|
||||
name: `MoviesClub - ${serverName}`,
|
||||
title: `${mediaType === 'movie' ? 'Movie' : `S${season.toString().padStart(2, '0')}E${episode.toString().padStart(2, '0')}`} ${tmdbId} - ${serverName}`,
|
||||
url: serverData,
|
||||
quality: extractQualityFromUrl(serverData) || '720p',
|
||||
headers: {}
|
||||
};
|
||||
streams.push(stream);
|
||||
}
|
||||
return streams;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle JSON response
|
||||
if (serverData && typeof serverData === 'object') {
|
||||
// Look for common stream URL patterns
|
||||
const possibleKeys = ['file', 'url', 'src', 'stream', 'video', 'source'];
|
||||
|
||||
for (const key of possibleKeys) {
|
||||
if (serverData[key] && typeof serverData[key] === 'string' && serverData[key].startsWith('http')) {
|
||||
console.log(`[moviesclub] Found stream URL in key '${key}': ${serverData[key].substring(0, 100)}`);
|
||||
|
||||
const stream = {
|
||||
name: `MoviesClub - ${serverName}`,
|
||||
title: `${mediaType === 'movie' ? 'Movie' : `S${season.toString().padStart(2, '0')}E${episode.toString().padStart(2, '0')}`} ${tmdbId} - ${serverName}`,
|
||||
url: serverData[key],
|
||||
quality: extractQualityFromUrl(serverData[key]) || '720p',
|
||||
headers: {}
|
||||
};
|
||||
|
||||
// Add poster if available
|
||||
if (serverData.poster) {
|
||||
stream.poster = serverData.poster;
|
||||
}
|
||||
|
||||
// Add subtitles if available
|
||||
if (serverData.subtitle || serverData.subtitles) {
|
||||
const subtitleData = serverData.subtitle || serverData.subtitles;
|
||||
stream.subtitles = parseSubtitles(subtitleData);
|
||||
}
|
||||
|
||||
streams.push(stream);
|
||||
break; // Only take the first valid stream URL
|
||||
}
|
||||
}
|
||||
|
||||
// If no direct URL found, check for nested objects
|
||||
if (streams.length === 0) {
|
||||
for (const key of possibleKeys) {
|
||||
if (serverData[key] && typeof serverData[key] === 'object') {
|
||||
const nestedStreams = extractStreamsFromServer(serverData[key], serverName, mediaType, season, episode, tmdbId);
|
||||
streams.push(...nestedStreams);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[moviesclub] Error extracting streams from server ${serverName}:`, error.message);
|
||||
}
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
// Extract stream URL from Playerjs configuration in HTML
|
||||
function extractStreamFromPlayerjs(html) {
|
||||
try {
|
||||
// Look for the Playerjs constructor call
|
||||
const playerMatch = html.match(/new Playerjs\(\s*(\{[^}]*\})\s*\)/);
|
||||
if (!playerMatch) {
|
||||
console.log('[moviesclub] No Playerjs constructor found in HTML');
|
||||
return null;
|
||||
}
|
||||
|
||||
const configStr = playerMatch[1];
|
||||
console.log(`[moviesclub] Found Playerjs config: ${configStr.substring(0, 200)}...`);
|
||||
|
||||
// Extract the file URL from the configuration
|
||||
const fileMatch = configStr.match(/file:\s*['"]([^'"]+)['"]/);
|
||||
if (!fileMatch) {
|
||||
console.log('[moviesclub] No file URL found in Playerjs config');
|
||||
return null;
|
||||
}
|
||||
|
||||
const streamUrl = fileMatch[1];
|
||||
console.log(`[moviesclub] Extracted stream URL from Playerjs: ${streamUrl.substring(0, 100)}...`);
|
||||
|
||||
return streamUrl;
|
||||
|
||||
} catch (error) {
|
||||
console.error('[moviesclub] Error extracting stream from Playerjs:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse player configuration from unpacked JavaScript
|
||||
function parsePlayerConfig(unpackedCode) {
|
||||
try {
|
||||
// Look for the player configuration object
|
||||
const configMatch = unpackedCode.match(/new Playerjs\(\s*(\{[^}]*\})\s*\)/);
|
||||
if (!configMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract the configuration object
|
||||
const configStr = configMatch[1];
|
||||
|
||||
// Parse the configuration (this is a simple parser for the expected format)
|
||||
const config = {};
|
||||
|
||||
// Extract key-value pairs
|
||||
const pairs = configStr.match(/(\w+):"([^"]*)"/g) || [];
|
||||
pairs.forEach(pair => {
|
||||
const [, key, value] = pair.match(/(\w+):"([^"]*)"/);
|
||||
config[key] = value;
|
||||
});
|
||||
|
||||
// Also handle non-quoted values
|
||||
const numberPairs = configStr.match(/(\w+):(\d+(?:\.\d+)?)/g) || [];
|
||||
numberPairs.forEach(pair => {
|
||||
const [, key, value] = pair.match(/(\w+):(\d+(?:\.\d+)?)/);
|
||||
config[key] = parseFloat(value);
|
||||
});
|
||||
|
||||
return config;
|
||||
} catch (error) {
|
||||
console.error('[moviesclub] Error parsing player config:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse subtitles from the subtitle string
|
||||
function parseSubtitles(subtitleStr) {
|
||||
if (!subtitleStr) return [];
|
||||
|
||||
const subtitles = [];
|
||||
const subtitleMatches = subtitleStr.match(/\[([^\]]+)\]([^,\[]*)/g);
|
||||
|
||||
if (subtitleMatches) {
|
||||
subtitleMatches.forEach(match => {
|
||||
const [, language, url] = match.match(/\[([^\]]+)\]([^,\[]*)/);
|
||||
if (url && url.trim()) {
|
||||
subtitles.push({
|
||||
language: language.trim(),
|
||||
url: url.trim(),
|
||||
type: 'vtt'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return subtitles;
|
||||
}
|
||||
|
||||
// Extract quality from URL
|
||||
function extractQualityFromUrl(url) {
|
||||
if (!url) return '720p';
|
||||
|
||||
const qualityPatterns = [
|
||||
{ pattern: /1080p|1080/i, quality: '1080p' },
|
||||
{ pattern: /720p|720/i, quality: '720p' },
|
||||
{ pattern: /480p|480/i, quality: '480p' },
|
||||
{ pattern: /360p|360/i, quality: '360p' },
|
||||
{ pattern: /2160p|4k|uhd/i, quality: '2160p' }
|
||||
];
|
||||
|
||||
for (const { pattern, quality } of qualityPatterns) {
|
||||
if (pattern.test(url)) {
|
||||
return quality;
|
||||
}
|
||||
}
|
||||
|
||||
return '720p';
|
||||
}
|
||||
|
||||
// Extract stream URL from Playerjs configuration in HTML (updated version)
|
||||
function extractStreamFromPlayerjsUpdated(html) {
|
||||
try {
|
||||
// Look for the Playerjs constructor call
|
||||
const playerMatch = html.match(/new Playerjs\(\s*(\{[^}]*\})\s*\)/);
|
||||
if (!playerMatch) {
|
||||
console.log('[moviesclub] No Playerjs constructor found in HTML');
|
||||
return null;
|
||||
}
|
||||
|
||||
const configStr = playerMatch[1];
|
||||
console.log(`[moviesclub] Found Playerjs config: ${configStr.substring(0, 200)}...`);
|
||||
|
||||
// Extract the file URL from the configuration
|
||||
let streamUrl = null;
|
||||
|
||||
// Try multiple patterns to extract the file URL
|
||||
const patterns = [
|
||||
/file:\s*['"]([^'"]+)['"]/,
|
||||
/file:\s*([^,\s}]+)/,
|
||||
/file\s*:\s*['"]([^'"]+)['"]/,
|
||||
/file\s*:\s*([^,\s}]+)/
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = configStr.match(pattern);
|
||||
if (match && match[1] && match[1].trim() !== '' && match[1].trim() !== '""') {
|
||||
streamUrl = match[1].replace(/['"]/g, '').trim();
|
||||
console.log(`[moviesclub] Found stream URL with pattern: ${streamUrl.substring(0, 100)}...`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no URL found in config, look for streaming URLs in the HTML
|
||||
if (!streamUrl) {
|
||||
console.log('[moviesclub] No file URL in Playerjs config, searching HTML for streaming URLs...');
|
||||
|
||||
// Look for m3u8 URLs
|
||||
const m3u8Match = html.match(/https?:\/\/[^'"\s]+\.m3u8[^'"\s]*/i);
|
||||
if (m3u8Match) {
|
||||
streamUrl = m3u8Match[0];
|
||||
console.log(`[moviesclub] Found m3u8 URL in HTML: ${streamUrl.substring(0, 100)}...`);
|
||||
}
|
||||
|
||||
// Look for other streaming URLs
|
||||
if (!streamUrl) {
|
||||
const streamMatch = html.match(/https?:\/\/[^'"\s]+(?:\.mp4|\.m3u8|\.ts)[^'"\s]*/i);
|
||||
if (streamMatch) {
|
||||
streamUrl = streamMatch[0];
|
||||
console.log(`[moviesclub] Found streaming URL in HTML: ${streamUrl.substring(0, 100)}...`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!streamUrl) {
|
||||
console.log('[moviesclub] No file URL found in Playerjs config after trying all patterns');
|
||||
console.log('[moviesclub] Config string:', configStr.substring(0, 500));
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`[moviesclub] Extracted stream URL from Playerjs: ${streamUrl.substring(0, 100)}...`);
|
||||
|
||||
return streamUrl;
|
||||
|
||||
} catch (error) {
|
||||
console.error('[moviesclub] Error extracting stream from Playerjs:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getMoviesClubStreams
|
||||
};
|
||||
+20
-99
@@ -25,13 +25,6 @@ function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
|
||||
// --- Proxy Configuration ---
|
||||
const MOVIESMOD_PROXY_URL = process.env.MOVIESMOD_PROXY_URL;
|
||||
if (MOVIESMOD_PROXY_URL) {
|
||||
console.log(`[MoviesMod] Proxy support enabled: ${MOVIESMOD_PROXY_URL}`);
|
||||
} else {
|
||||
console.log('[MoviesMod] No proxy configured, using direct connections');
|
||||
}
|
||||
|
||||
// --- Domain Fetching ---
|
||||
let moviesModDomain = 'https://moviesmod.chat'; // Fallback domain
|
||||
@@ -92,22 +85,14 @@ const saveToCache = async (key, data) => {
|
||||
// Initialize cache directory on startup
|
||||
ensureCacheDir();
|
||||
|
||||
// Proxy wrapper function
|
||||
// Request helper
|
||||
const makeRequest = async (url, options = {}) => {
|
||||
if (MOVIESMOD_PROXY_URL) {
|
||||
// Route through proxy
|
||||
const proxiedUrl = `${MOVIESMOD_PROXY_URL}${encodeURIComponent(url)}`;
|
||||
console.log(`[MoviesMod] Making proxied request to: ${url}`);
|
||||
return axios.get(proxiedUrl, options);
|
||||
} else {
|
||||
// Direct request
|
||||
console.log(`[MoviesMod] Making direct request to: ${url}`);
|
||||
return axios.get(url, options);
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to create a proxied session for SID resolution
|
||||
const createProxiedSession = async (jar) => {
|
||||
// Helper function to create a session for SID resolution
|
||||
const createSession = async (jar) => {
|
||||
const { wrapper } = await getAxiosCookieJarSupport();
|
||||
|
||||
const sessionConfig = {
|
||||
@@ -134,46 +119,6 @@ const createProxiedSession = async (jar) => {
|
||||
}
|
||||
};
|
||||
|
||||
// If proxy is enabled, wrap the session methods to use proxy
|
||||
if (MOVIESMOD_PROXY_URL) {
|
||||
console.log(`[MoviesMod] Creating SID session with proxy: ${MOVIESMOD_PROXY_URL}`);
|
||||
const originalGet = session.get.bind(session);
|
||||
const originalPost = session.post.bind(session);
|
||||
|
||||
session.get = async (url, options = {}) => {
|
||||
const proxiedUrl = `${MOVIESMOD_PROXY_URL}${encodeURIComponent(url)}`;
|
||||
console.log(`[MoviesMod] Making proxied SID GET request to: ${url}`);
|
||||
|
||||
// Extract cookies from jar and add to headers
|
||||
const cookieString = await getCookiesForUrl(url);
|
||||
if (cookieString) {
|
||||
console.log(`[MoviesMod] Adding cookies to proxied request: ${cookieString}`);
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
'Cookie': cookieString
|
||||
};
|
||||
}
|
||||
|
||||
return originalGet(proxiedUrl, options);
|
||||
};
|
||||
|
||||
session.post = async (url, data, options = {}) => {
|
||||
const proxiedUrl = `${MOVIESMOD_PROXY_URL}${encodeURIComponent(url)}`;
|
||||
console.log(`[MoviesMod] Making proxied SID POST request to: ${url}`);
|
||||
|
||||
// Extract cookies from jar and add to headers
|
||||
const cookieString = await getCookiesForUrl(url);
|
||||
if (cookieString) {
|
||||
console.log(`[MoviesMod] Adding cookies to proxied request: ${cookieString}`);
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
'Cookie': cookieString
|
||||
};
|
||||
}
|
||||
|
||||
return originalPost(proxiedUrl, data, options);
|
||||
};
|
||||
}
|
||||
|
||||
return session;
|
||||
};
|
||||
@@ -430,8 +375,8 @@ async function resolveTechUnblockedLink(sidUrl) {
|
||||
const { origin } = new URL(sidUrl);
|
||||
const jar = new CookieJar();
|
||||
|
||||
// Create session with proxy support
|
||||
const session = await createProxiedSession(jar);
|
||||
// Create session
|
||||
const session = await createSession(jar);
|
||||
|
||||
try {
|
||||
// Step 0: Get the _wp_http value
|
||||
@@ -679,24 +624,12 @@ async function resolveVideoSeedLink(videoSeedUrl) {
|
||||
const formData = new FormData();
|
||||
formData.append('keys', keys);
|
||||
|
||||
let apiResponse;
|
||||
if (MOVIESMOD_PROXY_URL) {
|
||||
const proxiedApiUrl = `${MOVIESMOD_PROXY_URL}${encodeURIComponent(apiUrl)}`;
|
||||
console.log(`[MoviesMod] Making proxied POST request to VideoSeed API`);
|
||||
apiResponse = await axios.post(proxiedApiUrl, formData, {
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
'x-token': new URL(videoSeedUrl).hostname
|
||||
}
|
||||
});
|
||||
} else {
|
||||
apiResponse = await axios.post(apiUrl, formData, {
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
'x-token': new URL(videoSeedUrl).hostname
|
||||
}
|
||||
});
|
||||
}
|
||||
const apiResponse = await axios.post(apiUrl, formData, {
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
'x-token': new URL(videoSeedUrl).hostname
|
||||
}
|
||||
});
|
||||
|
||||
if (apiResponse.data && apiResponse.data.url) {
|
||||
return apiResponse.data.url;
|
||||
@@ -724,27 +657,13 @@ async function validateVideoUrl(url, timeout = 10000) {
|
||||
try {
|
||||
console.log(`[MoviesMod] Validating URL: ${url.substring(0, 100)}...`);
|
||||
|
||||
// Use proxy for URL validation if enabled
|
||||
let response;
|
||||
if (MOVIESMOD_PROXY_URL) {
|
||||
const proxiedUrl = `${MOVIESMOD_PROXY_URL}${encodeURIComponent(url)}`;
|
||||
console.log(`[MoviesMod] Making proxied HEAD request for validation to: ${url}`);
|
||||
response = await axios.head(proxiedUrl, {
|
||||
timeout,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||
'Range': 'bytes=0-1' // Just request first byte to test
|
||||
}
|
||||
});
|
||||
} else {
|
||||
response = await axios.head(url, {
|
||||
timeout,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||
'Range': 'bytes=0-1' // Just request first byte to test
|
||||
}
|
||||
});
|
||||
}
|
||||
const response = await axios.head(url, {
|
||||
timeout,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||
'Range': 'bytes=0-1' // Just request first byte to test
|
||||
}
|
||||
});
|
||||
|
||||
// Check if status is OK (200-299) or partial content (206)
|
||||
if (response.status >= 200 && response.status < 400) {
|
||||
@@ -1187,6 +1106,8 @@ async function getMoviesModStreams(tmdbId, mediaType, seasonNum = null, episodeN
|
||||
title: `${cleanFileName}\n${sizeInfo || ''}${techDetailsString}`,
|
||||
url: selectedResult.url,
|
||||
quality: actualQuality,
|
||||
provider: 'moviesmod',
|
||||
headers: {}
|
||||
};
|
||||
} else {
|
||||
console.warn(`[MoviesMod] Unsupported URL type for final processing: ${targetLink?.url}`);
|
||||
|
||||
+157
-241
@@ -1,12 +1,23 @@
|
||||
const { config } = require('../utils/config');
|
||||
// Lazy load showbox to avoid early side-effects; temporarily disabled until stabilized
|
||||
let showbox = null;
|
||||
let k4hdhub = null; // 4khdhub provider lazy ref
|
||||
let moviesmod = null; // moviesmod provider lazy ref
|
||||
let mp4hydra = null; // MP4Hydra provider lazy ref
|
||||
let vidzee = null; // VidZee provider lazy ref
|
||||
let vixsrc = null; // vixsrc provider lazy ref
|
||||
let xprime = null; // xprime provider lazy ref
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Lazy load cache for providers
|
||||
const providerCache = new Map();
|
||||
|
||||
// Function name mappings for existing providers
|
||||
const providerFunctionMap = {
|
||||
'Showbox.js': 'getStreamsFromTmdbId',
|
||||
'4khdhub.js': 'get4KHDHubStreams',
|
||||
'moviesmod.js': 'getMoviesModStreams',
|
||||
'MP4Hydra.js': 'getMP4HydraStreams',
|
||||
'VidZee.js': 'getVidZeeStreams',
|
||||
'vixsrc.js': 'getVixsrcStreams',
|
||||
'xprime.js': 'getXprimeStreams',
|
||||
'uhdmovies.js': 'getUHDMoviesStreams',
|
||||
'moviesclub.js': 'getMoviesClubStreams',
|
||||
};
|
||||
|
||||
// Stats for debug endpoint
|
||||
let lastCookieStats = { selected: null, index: null, total: 0, remainingMB: null, timestamp: null };
|
||||
|
||||
@@ -14,248 +25,153 @@ async function getEffectiveCookies() {
|
||||
return Array.isArray(config.febboxCookies) ? config.febboxCookies : [];
|
||||
}
|
||||
|
||||
// Adapter for Showbox provider using existing exported getStreamsFromTmdbId
|
||||
async function fetchShowbox(ctx) {
|
||||
if (!showbox) {
|
||||
try { showbox = require('./Showbox.js'); } catch (e) { console.error('Failed to load Showbox provider:', e.message); return []; }
|
||||
}
|
||||
const { getTmdbApiKey } = require('../utils/tmdbKey');
|
||||
const tmdbApiKey = getTmdbApiKey();
|
||||
if (!tmdbApiKey) {
|
||||
console.warn('[registry] showbox skipped: TMDB API key missing');
|
||||
return [];
|
||||
}
|
||||
const cookies = await getEffectiveCookies();
|
||||
const previousConfig = global.currentRequestConfig;
|
||||
global.currentRequestConfig = { ...(previousConfig || {}) };
|
||||
let selected = null;
|
||||
if (cookies.length > 0) {
|
||||
const index = Math.floor(Math.random() * cookies.length);
|
||||
selected = cookies[index];
|
||||
global.currentRequestConfig.cookie = selected.startsWith('ui=') ? selected : `ui=${selected}`;
|
||||
global.currentRequestConfig.cookies = cookies.map(c => c.startsWith('ui=') ? c : `ui=${c}`);
|
||||
lastCookieStats = { selected: selected.slice(0, 16) + '...', index, total: cookies.length, remainingMB: null, timestamp: Date.now() };
|
||||
console.log(`[registry] Cookie random pick index=${index} total=${cookies.length}`);
|
||||
}
|
||||
try {
|
||||
const tmdbType = ctx.type === 'movie' ? 'movie' : 'tv';
|
||||
console.log(`[registry] Fetching Showbox streams for ${tmdbType} tmdbId=${ctx.tmdbId} season=${ctx.season||''} episode=${ctx.episode||''}`);
|
||||
const t0 = Date.now();
|
||||
const streams = await showbox.getStreamsFromTmdbId(tmdbType, ctx.tmdbId, ctx.season || null, ctx.episode || null, null, selected);
|
||||
const durationMs = Date.now() - t0;
|
||||
console.log(`[registry] Provider showbox fetch duration ${durationMs}ms`);
|
||||
if (global.currentRequestUserCookieRemainingMB != null) {
|
||||
lastCookieStats.remainingMB = global.currentRequestUserCookieRemainingMB;
|
||||
}
|
||||
console.log(`[registry] Showbox returned ${Array.isArray(streams)?streams.length:0} streams`);
|
||||
return Array.isArray(streams) ? streams : [];
|
||||
} catch (e) {
|
||||
console.error('[registry] Showbox fetch error:', e.message);
|
||||
return [];
|
||||
} finally {
|
||||
global.currentRequestConfig = previousConfig || {};
|
||||
}
|
||||
// Get all provider files
|
||||
function getProviderFiles() {
|
||||
const providersDir = path.join(__dirname);
|
||||
return fs.readdirSync(providersDir)
|
||||
.filter(file => file.endsWith('.js') && file !== 'registry.js' && file !== 'vidsrcextractor.js')
|
||||
.map(file => ({
|
||||
name: path.parse(file).name.toLowerCase(),
|
||||
file: file,
|
||||
functionName: providerFunctionMap[file]
|
||||
}));
|
||||
}
|
||||
|
||||
// Load a provider module
|
||||
function loadProvider(providerFile) {
|
||||
if (!providerCache.has(providerFile)) {
|
||||
try {
|
||||
providerCache.set(providerFile, require(path.join(__dirname, providerFile)));
|
||||
} catch (e) {
|
||||
console.error(`Failed to load provider ${providerFile}:`, e.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return providerCache.get(providerFile);
|
||||
}
|
||||
|
||||
// Create fetch function for a provider
|
||||
function createFetchFunction(providerInfo) {
|
||||
return async function(ctx) {
|
||||
const module = loadProvider(providerInfo.file);
|
||||
if (!module) return [];
|
||||
|
||||
const funcName = providerInfo.functionName;
|
||||
if (!module[funcName]) {
|
||||
console.error(`Provider ${providerInfo.name} does not export ${funcName}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const mediaType = ctx.type === 'movie' ? 'movie' : 'tv';
|
||||
const t0 = Date.now();
|
||||
|
||||
let result;
|
||||
if (providerInfo.name === 'showbox') {
|
||||
// Special case for Showbox with TMDB key and cookies
|
||||
const { getTmdbApiKey } = require('../utils/tmdbKey');
|
||||
const tmdbApiKey = getTmdbApiKey();
|
||||
if (!tmdbApiKey) {
|
||||
console.warn('[registry] showbox skipped: TMDB API key missing');
|
||||
return [];
|
||||
}
|
||||
const cookies = await getEffectiveCookies();
|
||||
const previousConfig = global.currentRequestConfig;
|
||||
global.currentRequestConfig = { ...(previousConfig || {}) };
|
||||
let selected = null;
|
||||
if (cookies.length > 0) {
|
||||
const index = Math.floor(Math.random() * cookies.length);
|
||||
selected = cookies[index];
|
||||
global.currentRequestConfig.cookie = selected.startsWith('ui=') ? selected : `ui=${selected}`;
|
||||
global.currentRequestConfig.cookies = cookies.map(c => c.startsWith('ui=') ? c : `ui=${c}`);
|
||||
lastCookieStats = { selected: selected.slice(0, 16) + '...', index, total: cookies.length, remainingMB: null, timestamp: Date.now() };
|
||||
console.log(`[registry] Cookie random pick index=${index} total=${cookies.length}`);
|
||||
}
|
||||
result = await module[funcName](mediaType, ctx.tmdbId, ctx.season || null, ctx.episode || null, null, selected);
|
||||
if (global.currentRequestUserCookieRemainingMB != null) {
|
||||
lastCookieStats.remainingMB = global.currentRequestUserCookieRemainingMB;
|
||||
}
|
||||
global.currentRequestConfig = previousConfig || {};
|
||||
} else if (providerInfo.name === 'xprime') {
|
||||
// Special case for Xprime with TMDB metadata lookup
|
||||
const { getTmdbApiKey } = require('../utils/tmdbKey');
|
||||
const tmdbApiKey = getTmdbApiKey();
|
||||
if (!tmdbApiKey) {
|
||||
console.warn('[registry] xprime skipped: TMDB API key missing');
|
||||
return [];
|
||||
}
|
||||
// Fetch minimal TMDB metadata for title + year
|
||||
let title = null; let year = null;
|
||||
try {
|
||||
const axios = require('axios');
|
||||
const metaUrl = `https://api.themoviedb.org/3/${mediaType}/${ctx.tmdbId}?api_key=${tmdbApiKey}`;
|
||||
const { data: meta } = await axios.get(metaUrl, { timeout: 8000 });
|
||||
title = mediaType === 'movie' ? (meta.title || meta.original_title) : (meta.name || meta.original_name);
|
||||
const dateStr = mediaType === 'movie' ? meta.release_date : meta.first_air_date;
|
||||
if (dateStr) year = dateStr.split('-')[0];
|
||||
} catch (mErr) {
|
||||
console.warn('[registry] xprime metadata fetch failed:', mErr.message);
|
||||
return [];
|
||||
}
|
||||
if (!title || !year) {
|
||||
console.log('[registry] xprime missing title/year after TMDB lookup');
|
||||
return [];
|
||||
}
|
||||
result = await module[funcName](title, year, mediaType, ctx.season || null, ctx.episode || null);
|
||||
// Normalize quality labels
|
||||
if (Array.isArray(result)) {
|
||||
result = result.map(s => {
|
||||
let q = s.quality || 'Unknown';
|
||||
if (!/(\d{3,4})p/.test(q.toLowerCase())) {
|
||||
const qLower = q.toLowerCase();
|
||||
if (qLower.includes('fhd') || qLower.includes('full')) q = '1080p';
|
||||
else if (qLower === 'hd' || qLower.includes('720')) q = '720p';
|
||||
else if (qLower.includes('sd') || qLower.includes('480')) q = '480p';
|
||||
else if (qLower.includes('cam')) q = '360p';
|
||||
}
|
||||
return { ...s, quality: q, name: s.name || 'Xprime', provider: 'xprime' };
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Standard provider call
|
||||
result = await module[funcName](ctx.tmdbId, mediaType, ctx.season || null, ctx.episode || null);
|
||||
}
|
||||
|
||||
const durationMs = Date.now() - t0;
|
||||
console.log(`[registry] ${providerInfo.name} fetch duration ${durationMs}ms`);
|
||||
|
||||
if (!Array.isArray(result)) return [];
|
||||
|
||||
// Add provider name if not present
|
||||
return result.map(s => ({ ...s, provider: s.provider || providerInfo.name }));
|
||||
|
||||
} catch (e) {
|
||||
console.error(`[registry] ${providerInfo.name} fetch error:`, e.message);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize providers
|
||||
const providerFiles = getProviderFiles();
|
||||
const providers = [];
|
||||
|
||||
if (config.enableShowboxProvider) {
|
||||
providers.push({ name: 'showbox', enabled: true, fetch: fetchShowbox });
|
||||
} else {
|
||||
console.log('[registry] showbox disabled via config');
|
||||
}
|
||||
for (const providerInfo of providerFiles) {
|
||||
const enableFlag = `enable${providerInfo.name.charAt(0).toUpperCase() + providerInfo.name.slice(1)}Provider`;
|
||||
const enabled = config[enableFlag] !== false;
|
||||
|
||||
|
||||
// 4KHDHub adapter
|
||||
async function fetch4khdhub(ctx) {
|
||||
if (!k4hdhub) {
|
||||
try { k4hdhub = require('./4khdhub.js'); } catch (e) { console.error('Failed to load 4khdhub provider:', e.message); return []; }
|
||||
}
|
||||
try {
|
||||
const mediaType = ctx.type === 'movie' ? 'movie' : 'tv';
|
||||
const t0 = Date.now();
|
||||
const result = await k4hdhub.get4KHDHubStreams(ctx.tmdbId, mediaType, ctx.season || null, ctx.episode || null);
|
||||
const durationMs = Date.now() - t0;
|
||||
console.log(`[registry] 4khdhub fetch duration ${durationMs}ms`);
|
||||
if (!Array.isArray(result)) return [];
|
||||
return result.map(s => ({ ...s, name: s.name || '4KHDHub', provider: '4khdhub' }));
|
||||
} catch (e) {
|
||||
console.error('[registry] 4khdhub fetch error:', e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (config.enable4khdhubProvider) {
|
||||
providers.push({ name: '4khdhub', enabled: true, fetch: fetch4khdhub });
|
||||
console.log('[registry] 4khdhub provider enabled');
|
||||
} else {
|
||||
console.log('[registry] 4khdhub disabled via config');
|
||||
}
|
||||
|
||||
// MoviesMod adapter
|
||||
async function fetchMoviesMod(ctx) {
|
||||
if (!moviesmod) {
|
||||
try { moviesmod = require('./moviesmod.js'); } catch (e) { console.error('Failed to load moviesmod provider:', e.message); return []; }
|
||||
}
|
||||
try {
|
||||
const mediaType = ctx.type === 'movie' ? 'movie' : 'tv';
|
||||
const t0 = Date.now();
|
||||
const result = await moviesmod.getMoviesModStreams(ctx.tmdbId, mediaType, ctx.season || null, ctx.episode || null);
|
||||
const durationMs = Date.now() - t0;
|
||||
console.log(`[registry] moviesmod fetch duration ${durationMs}ms`);
|
||||
if (!Array.isArray(result)) return [];
|
||||
return result.map(s => ({ ...s, name: s.name || 'MoviesMod', provider: 'moviesmod' }));
|
||||
} catch (e) {
|
||||
console.error('[registry] moviesmod fetch error:', e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (config.enableMoviesmodProvider) {
|
||||
providers.push({ name: 'moviesmod', enabled: true, fetch: fetchMoviesMod });
|
||||
console.log('[registry] moviesmod provider enabled');
|
||||
} else {
|
||||
console.log('[registry] moviesmod disabled via config');
|
||||
}
|
||||
|
||||
// MP4Hydra adapter
|
||||
async function fetchMp4hydra(ctx) {
|
||||
if (!mp4hydra) {
|
||||
try { mp4hydra = require('./MP4Hydra.js'); } catch (e) { console.error('Failed to load MP4Hydra provider:', e.message); return []; }
|
||||
}
|
||||
try {
|
||||
const mediaType = ctx.type === 'movie' ? 'movie' : 'tv';
|
||||
const t0 = Date.now();
|
||||
const result = await mp4hydra.getMP4HydraStreams(ctx.tmdbId, mediaType, ctx.season || null, ctx.episode || null);
|
||||
const durationMs = Date.now() - t0;
|
||||
console.log(`[registry] mp4hydra fetch duration ${durationMs}ms`);
|
||||
if (!Array.isArray(result)) return [];
|
||||
return result.map(s => ({ ...s, name: s.name || 'MP4Hydra', provider: 'mp4hydra' }));
|
||||
} catch (e) {
|
||||
console.error('[registry] mp4hydra fetch error:', e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (config.enableMp4hydraProvider) {
|
||||
providers.push({ name: 'mp4hydra', enabled: true, fetch: fetchMp4hydra });
|
||||
console.log('[registry] mp4hydra provider enabled');
|
||||
} else {
|
||||
console.log('[registry] mp4hydra disabled via config');
|
||||
}
|
||||
|
||||
// VidZee adapter
|
||||
async function fetchVidZee(ctx) {
|
||||
if (!vidzee) {
|
||||
try { vidzee = require('./VidZee.js'); } catch (e) { console.error('Failed to load VidZee provider:', e.message); return []; }
|
||||
}
|
||||
try {
|
||||
const mediaType = ctx.type === 'movie' ? 'movie' : 'tv';
|
||||
const t0 = Date.now();
|
||||
const result = await vidzee.getVidZeeStreams(ctx.tmdbId, mediaType, ctx.season || null, ctx.episode || null);
|
||||
const durationMs = Date.now() - t0;
|
||||
console.log(`[registry] vidzee fetch duration ${durationMs}ms`);
|
||||
if (!Array.isArray(result)) return [];
|
||||
return result.map(s => ({ ...s, name: s.name || 'VidZee', provider: 'vidzee' }));
|
||||
} catch (e) {
|
||||
console.error('[registry] vidzee fetch error:', e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (config.enableVidzeeProvider) {
|
||||
providers.push({ name: 'vidzee', enabled: true, fetch: fetchVidZee });
|
||||
console.log('[registry] vidzee provider enabled');
|
||||
} else {
|
||||
console.log('[registry] vidzee disabled via config');
|
||||
}
|
||||
|
||||
// vixsrc adapter
|
||||
async function fetchVixsrc(ctx) {
|
||||
if (!vixsrc) {
|
||||
try { vixsrc = require('./vixsrc.js'); } catch (e) { console.error('Failed to load vixsrc provider:', e.message); return []; }
|
||||
}
|
||||
try {
|
||||
const mediaType = ctx.type === 'movie' ? 'movie' : 'tv';
|
||||
const t0 = Date.now();
|
||||
const result = await vixsrc.getVixsrcStreams(ctx.tmdbId, mediaType, ctx.season || null, ctx.episode || null);
|
||||
const durationMs = Date.now() - t0;
|
||||
console.log(`[registry] vixsrc fetch duration ${durationMs}ms`);
|
||||
if (!Array.isArray(result)) return [];
|
||||
return result.map(s => ({ ...s, name: s.name || 'Vixsrc', provider: 'vixsrc' }));
|
||||
} catch (e) {
|
||||
console.error('[registry] vixsrc fetch error:', e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (config.enableVixsrcProvider) {
|
||||
providers.push({ name: 'vixsrc', enabled: true, fetch: fetchVixsrc });
|
||||
console.log('[registry] vixsrc provider enabled');
|
||||
} else {
|
||||
console.log('[registry] vixsrc disabled via config');
|
||||
}
|
||||
|
||||
// xprime adapter (requires TMDB metadata lookup for title/year)
|
||||
async function fetchXprime(ctx) {
|
||||
if (!xprime) {
|
||||
try { xprime = require('./xprime.js'); } catch (e) { console.error('Failed to load xprime provider:', e.message); return []; }
|
||||
}
|
||||
try {
|
||||
const mediaType = ctx.type === 'movie' ? 'movie' : 'tv';
|
||||
const { getTmdbApiKey } = require('../utils/tmdbKey');
|
||||
const tmdbApiKey = getTmdbApiKey();
|
||||
if (!tmdbApiKey) {
|
||||
console.warn('[registry] xprime skipped: TMDB API key missing');
|
||||
return [];
|
||||
}
|
||||
// Fetch minimal TMDB metadata for title + year
|
||||
let title = null; let year = null;
|
||||
try {
|
||||
const axios = require('axios');
|
||||
const metaUrl = `https://api.themoviedb.org/3/${mediaType}/${ctx.tmdbId}?api_key=${tmdbApiKey}`;
|
||||
const { data: meta } = await axios.get(metaUrl, { timeout: 8000 });
|
||||
title = mediaType === 'movie' ? (meta.title || meta.original_title) : (meta.name || meta.original_name);
|
||||
const dateStr = mediaType === 'movie' ? meta.release_date : meta.first_air_date;
|
||||
if (dateStr) year = dateStr.split('-')[0];
|
||||
} catch (mErr) {
|
||||
console.warn('[registry] xprime metadata fetch failed:', mErr.message);
|
||||
return [];
|
||||
}
|
||||
if (!title || !year) {
|
||||
console.log('[registry] xprime missing title/year after TMDB lookup');
|
||||
return [];
|
||||
}
|
||||
const t0 = Date.now();
|
||||
const streams = await xprime.getXprimeStreams(title, year, mediaType, ctx.season || null, ctx.episode || null, true, null);
|
||||
const durationMs = Date.now() - t0;
|
||||
console.log(`[registry] xprime fetch duration ${durationMs}ms (title='${title}' year=${year}) returned ${Array.isArray(streams)?streams.length:0}`);
|
||||
if (!Array.isArray(streams) || !streams.length) return [];
|
||||
// Normalize quality labels that are non-numeric (e.g., FHD/HD/SD)
|
||||
return streams.map(s => {
|
||||
let q = s.quality || 'Unknown';
|
||||
if (!/(\d{3,4})p/.test(q.toLowerCase())) {
|
||||
const qLower = q.toLowerCase();
|
||||
if (qLower.includes('fhd') || qLower.includes('full')) q = '1080p';
|
||||
else if (qLower === 'hd' || qLower.includes('720')) q = '720p';
|
||||
else if (qLower.includes('sd') || qLower.includes('480')) q = '480p';
|
||||
else if (qLower.includes('cam')) q = '360p';
|
||||
}
|
||||
return { ...s, quality: q, name: s.name || 'Xprime', provider: 'xprime' };
|
||||
if (enabled) {
|
||||
providers.push({
|
||||
name: providerInfo.name,
|
||||
enabled: true,
|
||||
fetch: createFetchFunction(providerInfo)
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('[registry] xprime fetch error:', e.message);
|
||||
return [];
|
||||
console.log(`[registry] ${providerInfo.name} provider enabled`);
|
||||
} else {
|
||||
console.log(`[registry] ${providerInfo.name} disabled via config`);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.enableXprimeProvider) {
|
||||
providers.push({ name: 'xprime', enabled: true, fetch: fetchXprime });
|
||||
console.log('[registry] xprime provider enabled');
|
||||
} else {
|
||||
console.log('[registry] xprime disabled via config');
|
||||
}
|
||||
|
||||
function listProviders() { return providers.map(p => ({ name: p.name, enabled: p.enabled })); }
|
||||
function getProvider(name) { return providers.find(p => p.name === name.toLowerCase()) || null; }
|
||||
|
||||
|
||||
+56
-147
@@ -15,13 +15,7 @@ const getAxiosCookieJarSupport = async () => {
|
||||
return axiosCookieJarSupport;
|
||||
};
|
||||
|
||||
// --- Proxy Configuration ---
|
||||
const UHDMOVIES_PROXY_URL = process.env.UHDMOVIES_PROXY_URL;
|
||||
if (UHDMOVIES_PROXY_URL) {
|
||||
console.log(`[UHDMovies] Proxy support enabled: ${UHDMOVIES_PROXY_URL}`);
|
||||
} else {
|
||||
console.log('[UHDMovies] No proxy configured, using direct connections');
|
||||
}
|
||||
// All requests are direct
|
||||
|
||||
// --- Domain Fetching ---
|
||||
let uhdMoviesDomain = 'https://uhdmovies.email'; // Fallback domain
|
||||
@@ -52,9 +46,9 @@ async function getUHDMoviesDomain() {
|
||||
|
||||
// Constants
|
||||
const { getTmdbApiKey } = require('../utils/tmdbKey');
|
||||
function getUhdMoviesKey(){
|
||||
function getUhdMoviesKey() {
|
||||
const k = getTmdbApiKey();
|
||||
if(!k) console.warn('[UHDMovies] TMDB API key missing; certain metadata features skipped.');
|
||||
if (!k) console.warn('[UHDMovies] TMDB API key missing; certain metadata features skipped.');
|
||||
return k;
|
||||
}
|
||||
|
||||
@@ -91,7 +85,6 @@ const saveToCache = async (key, data) => {
|
||||
ensureCacheDir();
|
||||
|
||||
// Configure axios with headers to mimic a browser
|
||||
// Configure axios instance with optional proxy support
|
||||
const createAxiosInstance = () => {
|
||||
const config = {
|
||||
headers: {
|
||||
@@ -105,32 +98,15 @@ const createAxiosInstance = () => {
|
||||
timeout: 30000
|
||||
};
|
||||
|
||||
// Add proxy configuration if UHDMOVIES_PROXY_URL is set
|
||||
if (UHDMOVIES_PROXY_URL) {
|
||||
console.log(`[UHDMovies] Using proxy: ${UHDMOVIES_PROXY_URL}`);
|
||||
// For proxy URLs that expect the destination URL as a parameter
|
||||
config.transformRequest = [(data, headers) => {
|
||||
return data;
|
||||
}];
|
||||
}
|
||||
|
||||
return axios.create(config);
|
||||
};
|
||||
|
||||
const axiosInstance = createAxiosInstance();
|
||||
|
||||
// Proxy wrapper function
|
||||
// Request helper
|
||||
const makeRequest = async (url, options = {}) => {
|
||||
if (UHDMOVIES_PROXY_URL) {
|
||||
// Route through proxy
|
||||
const proxiedUrl = `${UHDMOVIES_PROXY_URL}${encodeURIComponent(url)}`;
|
||||
console.log(`[UHDMovies] Making proxied request to: ${url}`);
|
||||
return axiosInstance.get(proxiedUrl, options);
|
||||
} else {
|
||||
// Direct request
|
||||
console.log(`[UHDMovies] Making direct request to: ${url}`);
|
||||
return axiosInstance.get(url, options);
|
||||
}
|
||||
console.log(`[UHDMovies] Making direct request to: ${url}`);
|
||||
return axiosInstance.get(url, options);
|
||||
};
|
||||
|
||||
// Removed unused in-memory cache structure (uhdMoviesCache)
|
||||
@@ -233,8 +209,8 @@ function extractCleanQuality(fullQualityText) {
|
||||
// Fallback: try to extract a shorter version of the original text
|
||||
// Look for patterns like "Movie Name (Year) Resolution ..."
|
||||
const patterns = [
|
||||
/(\d{3,4}p.*?(?:x264|x265|hevc).*?)[[(]/i,
|
||||
/(\d{3,4}p.*?)[[(]/i,
|
||||
/(\d{3,4}p.*?(?:x264|x265|hevc).*?)[[(]/i,
|
||||
/(\d{3,4}p.*?)[[(]/i,
|
||||
/((?:720p|1080p|2160p|4k).*?)$/i
|
||||
];
|
||||
|
||||
@@ -275,7 +251,7 @@ async function extractTvShowDownloadLinks(showPageUrl, season, episode) {
|
||||
// Check if we are entering a new season block
|
||||
if (seasonMatch) {
|
||||
const currentSeasonNum = parseInt(seasonMatch[1], 10);
|
||||
if (currentSeasonNum === season) {
|
||||
if (currentSeasonNum === season) {
|
||||
inTargetSeason = true;
|
||||
console.log(`[UHDMovies] Entering Season ${season} block.`);
|
||||
} else if (inTargetSeason) {
|
||||
@@ -322,7 +298,7 @@ async function extractTvShowDownloadLinks(showPageUrl, season, episode) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- ENHANCED: Check for maxbutton-gdrive-episode structure ---
|
||||
if ($el.is('p') && $el.find('a.maxbutton-gdrive-episode').length > 0) {
|
||||
const episodeRegex = new RegExp(`^Episode\\s+0*${episode}(?!\\d)`, 'i');
|
||||
@@ -350,24 +326,24 @@ async function extractTvShowDownloadLinks(showPageUrl, season, episode) {
|
||||
|
||||
if (downloadLinks.length === 0) {
|
||||
console.log('[UHDMovies] Main extraction logic failed. Checking if requested season exists on page before fallback.');
|
||||
|
||||
|
||||
// Check if the requested season exists on the page at all
|
||||
let seasonExists = false;
|
||||
const actualSeasonsOnPage = new Set(); // Track what seasons actually have content
|
||||
|
||||
|
||||
// First pass: Look for actual episode content to see what seasons are available
|
||||
$('.entry-content').find('a[href*="tech.unblockedgames.world"], a[href*="tech.examzculture.in"], a.maxbutton-gdrive-episode').each((index, element) => {
|
||||
const $el = $(element);
|
||||
const linkText = $el.text().trim();
|
||||
const episodeText = $el.find('.mb-text').text().trim() || linkText;
|
||||
|
||||
|
||||
// Look for season indicators in episode links
|
||||
const seasonMatches = [
|
||||
episodeText.match(/S(\d{1,2})/i), // S01, S02, etc.
|
||||
episodeText.match(/Season\s+(\d+)/i), // Season 1, Season 2, etc.
|
||||
episodeText.match(/S(\d{1,2})E(\d{1,3})/i) // S01E01 format
|
||||
];
|
||||
|
||||
|
||||
for (const match of seasonMatches) {
|
||||
if (match && match[1]) {
|
||||
const foundSeason = parseInt(match[1], 10);
|
||||
@@ -375,9 +351,9 @@ async function extractTvShowDownloadLinks(showPageUrl, season, episode) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`[UHDMovies] Actual seasons found on page: ${Array.from(actualSeasonsOnPage).sort((a,b) => a-b).join(', ')}`);
|
||||
|
||||
|
||||
console.log(`[UHDMovies] Actual seasons found on page: ${Array.from(actualSeasonsOnPage).sort((a, b) => a - b).join(', ')}`);
|
||||
|
||||
// Check if requested season is in the actual content
|
||||
if (actualSeasonsOnPage.has(season)) {
|
||||
seasonExists = true;
|
||||
@@ -395,7 +371,7 @@ async function extractTvShowDownloadLinks(showPageUrl, season, episode) {
|
||||
text.match(/Season\s+\d+\s*[–-]\s*(\d+)/i), // Matches "Season 1-2"
|
||||
text.match(/\bS(\d+)/i) // Matches "S2", "S02", etc.
|
||||
];
|
||||
|
||||
|
||||
for (const match of seasonMatches) {
|
||||
if (match) {
|
||||
const currentSeasonNum = parseInt(match[1], 10);
|
||||
@@ -421,15 +397,15 @@ async function extractTvShowDownloadLinks(showPageUrl, season, episode) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (!seasonExists) {
|
||||
console.log(`[UHDMovies] Season ${season} not found on page. Available seasons may not include the requested season.`);
|
||||
// Don't use fallback if the season doesn't exist to avoid wrong episodes
|
||||
return { title: showTitle, links: [], seasonNotFound: true };
|
||||
}
|
||||
|
||||
|
||||
console.log(`[UHDMovies] Season ${season} exists on page but episode extraction failed. Trying fallback method with season filtering.`);
|
||||
|
||||
|
||||
// --- ENHANCED FALLBACK LOGIC FOR NEW HTML STRUCTURE ---
|
||||
// Try the new maxbutton-gdrive-episode structure first
|
||||
$('.entry-content').find('a.maxbutton-gdrive-episode').each((i, el) => {
|
||||
@@ -441,7 +417,7 @@ async function extractTvShowDownloadLinks(showPageUrl, season, episode) {
|
||||
const link = linkElement.attr('href');
|
||||
if (link && !downloadLinks.some(item => item.link === link)) {
|
||||
let qualityText = 'Unknown Quality';
|
||||
|
||||
|
||||
// Look for quality info in the preceding paragraph or heading
|
||||
const parentP = linkElement.closest('p, div');
|
||||
const prevElement = parentP.prev();
|
||||
@@ -461,7 +437,7 @@ async function extractTvShowDownloadLinks(showPageUrl, season, episode) {
|
||||
new RegExp(`Season\\s+0*${season}\\b`, 'i'), // Season 1
|
||||
new RegExp(`S0*${season}`, 'i') // S01 anywhere
|
||||
];
|
||||
|
||||
|
||||
const seasonMatch = seasonCheckRegexes.some(regex => regex.test(qualityText));
|
||||
if (!seasonMatch) {
|
||||
console.log(`[UHDMovies] Skipping episode from different season: Quality='${qualityText}'`);
|
||||
@@ -478,7 +454,7 @@ async function extractTvShowDownloadLinks(showPageUrl, season, episode) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// If still no results, try the original fallback logic
|
||||
if (downloadLinks.length === 0) {
|
||||
console.log(`[UHDMovies] Enhanced fallback failed, trying original fallback logic.`);
|
||||
@@ -508,7 +484,7 @@ async function extractTvShowDownloadLinks(showPageUrl, season, episode) {
|
||||
new RegExp(`Season\\s+0*${season}\\b`, 'i'), // Season 1
|
||||
new RegExp(`S0*${season}`, 'i') // S01 anywhere
|
||||
];
|
||||
|
||||
|
||||
const seasonMatch = seasonCheckRegexes.some(regex => regex.test(qualityText));
|
||||
if (!seasonMatch) {
|
||||
console.log(`[UHDMovies] Skipping episode from different season: Quality='${qualityText}'`);
|
||||
@@ -738,24 +714,12 @@ async function tryInstantDownload($) {
|
||||
const formData = new FormData();
|
||||
formData.append('keys', keys);
|
||||
|
||||
let apiResponse;
|
||||
if (UHDMOVIES_PROXY_URL) {
|
||||
const proxiedApiUrl = `${UHDMOVIES_PROXY_URL}${encodeURIComponent(apiUrl)}`;
|
||||
console.log(`[UHDMovies] Making proxied POST request for Instant Download API to: ${apiUrl}`);
|
||||
apiResponse = await axiosInstance.post(proxiedApiUrl, formData, {
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
'x-token': new URL(instantDownloadLink).hostname
|
||||
}
|
||||
});
|
||||
} else {
|
||||
apiResponse = await axiosInstance.post(apiUrl, formData, {
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
'x-token': new URL(instantDownloadLink).hostname
|
||||
}
|
||||
});
|
||||
}
|
||||
const apiResponse = await axiosInstance.post(apiUrl, formData, {
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
'x-token': new URL(instantDownloadLink).hostname
|
||||
}
|
||||
});
|
||||
|
||||
if (apiResponse.data && apiResponse.data.url) {
|
||||
let finalUrl = apiResponse.data.url;
|
||||
@@ -858,26 +822,13 @@ async function validateVideoUrl(url, timeout = 10000) {
|
||||
|
||||
try {
|
||||
console.log(`[UHDMovies] Validating URL: ${url.substring(0, 100)}...`);
|
||||
|
||||
// Use proxy for URL validation if enabled
|
||||
let response;
|
||||
if (UHDMOVIES_PROXY_URL) {
|
||||
const proxiedUrl = `${UHDMOVIES_PROXY_URL}${encodeURIComponent(url)}`;
|
||||
console.log(`[UHDMovies] Making proxied HEAD request for validation to: ${url}`);
|
||||
response = await axiosInstance.head(proxiedUrl, {
|
||||
timeout,
|
||||
headers: {
|
||||
'Range': 'bytes=0-1' // Just request first byte to test
|
||||
}
|
||||
});
|
||||
} else {
|
||||
response = await axiosInstance.head(url, {
|
||||
timeout,
|
||||
headers: {
|
||||
'Range': 'bytes=0-1' // Just request first byte to test
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const response = await axiosInstance.head(url, {
|
||||
timeout,
|
||||
headers: {
|
||||
'Range': 'bytes=0-1'
|
||||
}
|
||||
});
|
||||
|
||||
// Check if status is OK (200-299) or partial content (206)
|
||||
if (response.status >= 200 && response.status < 400) {
|
||||
@@ -1000,7 +951,7 @@ function scoreResult(title, requestedSeason = null) {
|
||||
console.log(`[UHDMovies] Season range bonus (+50): ${startSeason}-${endSeason} includes requested season ${requestedSeason}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check for specific season mentions
|
||||
const specificSeasonMatch = lowerTitle.match(/season\s+(\d+)/i);
|
||||
if (specificSeasonMatch) {
|
||||
@@ -1013,7 +964,7 @@ function scoreResult(title, requestedSeason = null) {
|
||||
console.log(`[UHDMovies] Season penalty (-20): Season ${mentionedSeason} is older than requested season ${requestedSeason}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check for "Season X Added" or similar indicators
|
||||
if (lowerTitle.includes('season') && lowerTitle.includes('added')) {
|
||||
const addedSeasonMatch = lowerTitle.match(/season\s+(\d+)\s+added/i);
|
||||
@@ -1076,10 +1027,10 @@ const getCookiesForUrl = async (jar, url) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
// Helper function to create a proxied session for SID resolution
|
||||
const createProxiedSession = async (jar) => {
|
||||
// Helper function to create a session for SID resolution
|
||||
const createSession = async (jar) => {
|
||||
const { wrapper } = await getAxiosCookieJarSupport();
|
||||
|
||||
|
||||
const sessionConfig = {
|
||||
jar,
|
||||
headers: {
|
||||
@@ -1093,46 +1044,7 @@ const createProxiedSession = async (jar) => {
|
||||
|
||||
const session = wrapper(axios.create(sessionConfig));
|
||||
|
||||
// If proxy is enabled, wrap the session methods to use proxy
|
||||
if (UHDMOVIES_PROXY_URL) {
|
||||
console.log(`[UHDMovies] Creating SID session with proxy: ${UHDMOVIES_PROXY_URL}`);
|
||||
const originalGet = session.get.bind(session);
|
||||
const originalPost = session.post.bind(session);
|
||||
|
||||
session.get = async (url, options = {}) => {
|
||||
const proxiedUrl = `${UHDMOVIES_PROXY_URL}${encodeURIComponent(url)}`;
|
||||
console.log(`[UHDMovies] Making proxied SID GET request to: ${url}`);
|
||||
|
||||
// Extract cookies from jar and add to headers
|
||||
const cookieString = await getCookiesForUrl(jar, url);
|
||||
if (cookieString) {
|
||||
console.log(`[UHDMovies] Adding cookies to proxied request: ${cookieString}`);
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
'Cookie': cookieString
|
||||
};
|
||||
}
|
||||
|
||||
return originalGet(proxiedUrl, options);
|
||||
};
|
||||
|
||||
session.post = async (url, data, options = {}) => {
|
||||
const proxiedUrl = `${UHDMOVIES_PROXY_URL}${encodeURIComponent(url)}`;
|
||||
console.log(`[UHDMovies] Making proxied SID POST request to: ${url}`);
|
||||
|
||||
// Extract cookies from jar and add to headers
|
||||
const cookieString = await getCookiesForUrl(jar, url);
|
||||
if (cookieString) {
|
||||
console.log(`[UHDMovies] Adding cookies to proxied request: ${cookieString}`);
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
'Cookie': cookieString
|
||||
};
|
||||
}
|
||||
|
||||
return originalPost(proxiedUrl, data, options);
|
||||
};
|
||||
}
|
||||
|
||||
return session;
|
||||
};
|
||||
@@ -1143,8 +1055,8 @@ async function resolveSidToDriveleech(sidUrl) {
|
||||
const { origin } = new URL(sidUrl);
|
||||
const jar = new CookieJar();
|
||||
|
||||
// Create session with proxy support
|
||||
const session = await createProxiedSession(jar);
|
||||
// Create session
|
||||
const session = await createSession(jar);
|
||||
|
||||
try {
|
||||
// Step 0: Get the _wp_http value
|
||||
@@ -1266,8 +1178,8 @@ async function getUHDMoviesStreams(tmdbId, mediaType = 'movie', season = null, e
|
||||
}
|
||||
console.log(`[UHDMovies] Cache MISS for ${cacheKey}. Fetching from source.`);
|
||||
// 2. If cache miss, get TMDB info to perform search
|
||||
const TMDB_API_KEY_UHDMOVIES = getUhdMoviesKey();
|
||||
const tmdbUrl = `https://api.themoviedb.org/3/${mediaType === 'tv' ? 'tv' : 'movie'}/${tmdbId}?api_key=${TMDB_API_KEY_UHDMOVIES}`;
|
||||
const TMDB_API_KEY_UHDMOVIES = getUhdMoviesKey();
|
||||
const tmdbUrl = `https://api.themoviedb.org/3/${mediaType === 'tv' ? 'tv' : 'movie'}/${tmdbId}?api_key=${TMDB_API_KEY_UHDMOVIES}`;
|
||||
const tmdbResponse = await axios.get(tmdbUrl);
|
||||
const tmdbData = tmdbResponse.data;
|
||||
const mediaInfo = {
|
||||
@@ -1337,25 +1249,25 @@ async function getUHDMoviesStreams(tmdbId, mediaType = 'movie', season = null, e
|
||||
|
||||
// 5. Extract SID links from the movie/show page
|
||||
let downloadInfo = await (mediaType === 'tv' ? extractTvShowDownloadLinks(matchingResult.link, season, episode) : extractDownloadLinks(matchingResult.link, mediaInfo.year));
|
||||
|
||||
|
||||
// Check if season was not found or episode extraction failed, and we have multiple results to try
|
||||
if (downloadInfo.links.length === 0 && matchingResults.length > 1 && scoredResults &&
|
||||
(downloadInfo.seasonNotFound || (mediaType === 'tv' && downloadInfo.title))) {
|
||||
if (downloadInfo.links.length === 0 && matchingResults.length > 1 && scoredResults &&
|
||||
(downloadInfo.seasonNotFound || (mediaType === 'tv' && downloadInfo.title))) {
|
||||
console.log(`[UHDMovies] Season ${season} not found or episode extraction failed on best match. Trying next best match...`);
|
||||
|
||||
|
||||
// Try the next best match
|
||||
const nextBestMatch = scoredResults[1];
|
||||
console.log(`[UHDMovies] Trying next best match: "${nextBestMatch.title}"`);
|
||||
|
||||
|
||||
downloadInfo = await (mediaType === 'tv' ? extractTvShowDownloadLinks(nextBestMatch.link, season, episode) : extractDownloadLinks(nextBestMatch.link, mediaInfo.year));
|
||||
|
||||
|
||||
if (downloadInfo.links.length > 0) {
|
||||
console.log(`[UHDMovies] Successfully found links on next best match!`);
|
||||
} else {
|
||||
console.log(`[UHDMovies] Next best match also failed. No download links found.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (downloadInfo.links.length === 0) {
|
||||
console.log('[UHDMovies] No download links found on page.');
|
||||
// Don't cache empty results to allow retrying later
|
||||
@@ -1468,11 +1380,8 @@ async function getUHDMoviesStreams(tmdbId, mediaType = 'movie', season = null, e
|
||||
title: `${cleanFileName}\n${sizeInfo}`,
|
||||
url: finalUrl,
|
||||
quality: linkInfo.quality,
|
||||
size: sizeInfo,
|
||||
fileName: fileName,
|
||||
fullTitle: rawQuality,
|
||||
codecs: codecs,
|
||||
behaviorHints: { bingeGroup: `uhdmovies-${linkInfo.quality}` }
|
||||
provider: 'uhdmovies',
|
||||
headers: {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
const cheerio = require('cheerio');
|
||||
// --- Constants ---
|
||||
const VIDSRC_PROXY_URL = process.env.VIDSRC_PROXY_URL;
|
||||
let BASEDOM = "https://cloudnestra.com"; // This can be updated by serversLoad
|
||||
const SOURCE_URL = "https://vidsrc.xyz/embed";
|
||||
// --- Helper: Conditional Proxied Fetch ---
|
||||
// This function wraps the native fetch. If VIDSRC_PROXY_URL is set in the environment,
|
||||
// it routes requests through the proxy. Otherwise, it makes a direct request.
|
||||
// --- Helper: Direct Fetch ---
|
||||
async function fetchWrapper(url, options) {
|
||||
if (VIDSRC_PROXY_URL) {
|
||||
const proxiedUrl = `${VIDSRC_PROXY_URL}${encodeURIComponent(url)}`;
|
||||
console.log(`[VidSrc Proxy] Fetching: ${url} via proxy`);
|
||||
// Note: The proxy will handle the actual fetching, so we send the request to the proxy URL.
|
||||
// We pass the original headers in the options, the proxy should forward them.
|
||||
return fetch(proxiedUrl, options);
|
||||
}
|
||||
// If no proxy is set, fetch directly.
|
||||
console.log(`[VidSrc Direct] Fetching: ${url}`);
|
||||
return fetch(url, options);
|
||||
}
|
||||
|
||||
+1
-1
@@ -195,7 +195,7 @@ function getVixsrcStreams(tmdbId, mediaType = 'movie', seasonNum = null, episode
|
||||
title: "Auto Quality Stream",
|
||||
url: masterPlaylistUrl,
|
||||
quality: '1080p',
|
||||
type: 'direct',
|
||||
provider: 'Vidsrc',
|
||||
headers: {
|
||||
'Referer': BASE_URL,
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
|
||||
|
||||
+7
-15
@@ -4,7 +4,6 @@ const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const XPRIME_PROXY_URL = process.env.XPRIME_PROXY_URL || null;
|
||||
const RETRY_DELAY_MS_XPRIME = 1000;
|
||||
const MAX_RETRIES_XPRIME = 3;
|
||||
|
||||
@@ -105,7 +104,7 @@ async function fetchStreamSize(url) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getXprimeStreams(title, year, type, seasonNum, episodeNum, useProxy = true) {
|
||||
async function getXprimeStreams(title, year, type, seasonNum, episodeNum) {
|
||||
if (!title || !year) return [];
|
||||
const encoded = encodeURIComponent(title);
|
||||
let apiUrl;
|
||||
@@ -116,19 +115,12 @@ async function getXprimeStreams(title, year, type, seasonNum, episodeNum, usePro
|
||||
apiUrl = `https://backend.xprime.tv/primebox?name=${encoded}&year=${year}&fallback_year=${year}&season=${seasonNum}&episode=${episodeNum}`;
|
||||
} else return [];
|
||||
|
||||
console.log(`[Xprime.tv] Fetch attempt '${title}' (${year}) type=${type} useProxy=${useProxy}`);
|
||||
console.log(`[Xprime.tv] Fetch attempt '${title}' (${year}) type=${type}`);
|
||||
|
||||
let resJson;
|
||||
try {
|
||||
if (useProxy && XPRIME_PROXY_URL) {
|
||||
const base = XPRIME_PROXY_URL.replace(/\/$/, '');
|
||||
const proxied = `${base}/?destination=${encodeURIComponent(apiUrl)}`;
|
||||
const r = await fetchWithRetry(proxied, { headers: BROWSER_HEADERS_XPRIME });
|
||||
resJson = await r.json();
|
||||
} else {
|
||||
const r = await fetchWithRetry(apiUrl, { headers: BROWSER_HEADERS_XPRIME });
|
||||
resJson = await r.json();
|
||||
}
|
||||
const r = await fetchWithRetry(apiUrl, { headers: BROWSER_HEADERS_XPRIME });
|
||||
resJson = await r.json();
|
||||
} catch (e) {
|
||||
console.error('[Xprime.tv] Fetch failed:', e.message);
|
||||
return [];
|
||||
@@ -140,12 +132,12 @@ async function getXprimeStreams(title, year, type, seasonNum, episodeNum, usePro
|
||||
Object.entries(item.streams).forEach(([quality, url]) => {
|
||||
if (!url || typeof url !== 'string') return;
|
||||
streams.push({
|
||||
name: title,
|
||||
title: `${title} - ${type === 'tv' ? `S${String(seasonNum).padStart(2,'0')}E${String(episodeNum).padStart(2,'0')} ` : ''}${quality}`,
|
||||
url,
|
||||
quality: quality || 'Unknown',
|
||||
title: `${title} - ${type === 'tv' ? `S${String(seasonNum).padStart(2,'0')}E${String(episodeNum).padStart(2,'0')} ` : ''}${quality}`,
|
||||
provider: 'Xprime.tv',
|
||||
codecs: [],
|
||||
size: 'Unknown size'
|
||||
headers: {},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
+40
-9
@@ -12,6 +12,8 @@
|
||||
<div class="brand">TMDB Embed API</div>
|
||||
<nav class="nav">
|
||||
<button class="nav-item" id="logoutBtn" data-action="logout">Logout</button>
|
||||
<button class="nav-item" id="restartBtn" type="button">Restart Server</button>
|
||||
<div class="nav-sep" role="separator" aria-hidden="true"></div>
|
||||
<button class="nav-item" data-target="panel-status">Server Status</button>
|
||||
<button class="nav-item" data-target="panel-core">Core</button>
|
||||
<button class="nav-item" data-target="panel-quality">Quality / Filters</button>
|
||||
@@ -97,8 +99,9 @@
|
||||
</label>
|
||||
<textarea name="minQualitiesRaw" class="mini quality-json" placeholder='{"default":"900p"}' disabled></textarea>
|
||||
</div>
|
||||
<div class="field codec-group"><label>Exclude Codecs<p class="hint">Preset radios serialize to a simple string. Custom & advanced modes use the JSON fields.</p>
|
||||
<div class="field codec-group"><label>Exclude Codecs<p class="hint">Preset radios serialize to JSON. Advanced lets you edit the JSON directly.</p>
|
||||
<div class="codec-presets" id="codecPresets">
|
||||
<label class="c-item"><input type="radio" name="codecPreset" value="none"> <span>None</span></label>
|
||||
<label class="c-item"><input type="radio" name="codecPreset" value="all"> <span>All</span></label>
|
||||
<label class="c-item"><input type="radio" name="codecPreset" value="dv"> <span>Dolby Vision (DV)</span></label>
|
||||
<label class="c-item"><input type="radio" name="codecPreset" value="hdr"> <span>HDR / HDR10(+)</span></label>
|
||||
@@ -172,20 +175,13 @@
|
||||
<p class="hint">These settings allow you to customize the behavior of the api and will effect timings.</p>
|
||||
<div class="field adv-flag-grid" id="advFlags" aria-label="Core Feature Flags">
|
||||
<label class="adv-flag-item"><input type="checkbox" name="adv_disableCache" value="1" /> <span>Disable Cache</span></label>
|
||||
<label class="adv-flag-item"><input type="checkbox" name="adv_showboxRotatingProxy" value="1" /> <span>Showbox Rotating Proxy</span></label>
|
||||
<label class="adv-flag-item"><input type="checkbox" name="adv_disableUrlValidation" value="1" /> <span>Disable URL Validation</span></label>
|
||||
<label class="adv-flag-item"><input type="checkbox" name="adv_disable4khdhubValidation" value="1" /> <span>Disable 4KHDHub Validation</span></label>
|
||||
</div>
|
||||
<h1>Proxy / Paths</h1>
|
||||
<h1>Paths</h1>
|
||||
<p class="hint">Provider enable/disable & feature flags apply immediately. Click toggles to enable (highlighted) or disable (dim).</p>
|
||||
<div class="adv-group">
|
||||
<div class="field small"><label>Showbox Cache Dir <input type="text" name="adv_showboxCacheDir" placeholder="/data/showbox-cache"/></label></div>
|
||||
<div class="field small"><label>Showbox Proxy (Primary) <input type="text" name="adv_showboxProxyPrimary" placeholder="https://proxy.example/"/></label></div>
|
||||
<div class="field small"><label>Showbox Proxy (Alternate) <input type="text" name="adv_showboxProxyAlternate" placeholder="https://proxy2.example/"/></label></div>
|
||||
<div class="field small"><label>Xprime Proxy URL <input type="text" name="adv_xprimeProxyUrl" placeholder="https://..."/></label></div>
|
||||
<div class="field small"><label>VidZee Proxy URL <input type="text" name="adv_vidzeeProxyUrl" placeholder="https://..."/></label></div>
|
||||
<div class="field small"><label>VidSrc Proxy URL <input type="text" name="adv_vidsrcProxyUrl" placeholder="https://..."/></label></div>
|
||||
<div class="field small"><label>MoviesMod Proxy URL <input type="text" name="adv_moviesmodProxyUrl" placeholder="https://..."/></label></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="panel-live" class="panel">
|
||||
@@ -206,4 +202,39 @@
|
||||
<span id="validationSummary" class="validation-summary"></span>
|
||||
</div>
|
||||
</main>
|
||||
<!-- Confirmation Modal -->
|
||||
<div id="confirmReset" class="modal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="confirmResetTitle">
|
||||
<div class="modal-backdrop"></div>
|
||||
<div class="modal-window">
|
||||
<h3 id="confirmResetTitle">Reset All Settings?</h3>
|
||||
<p class="modal-text">This will remove all overrides, delete all TMDB API keys and FebBox cookies, and restore provider and feature flags to their defaults. This action cannot be undone.</p>
|
||||
<ul class="modal-list">
|
||||
<li>Clear TMDB keys and FebBox cookies</li>
|
||||
<li>Reset provider enable/default selections</li>
|
||||
<li>Reapply default quality and validation settings</li>
|
||||
</ul>
|
||||
<label class="modal-opt"><input type="checkbox" id="confirmResetDontAsk" /> Don't ask again on this device</label>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="danger" data-action="confirm">Reset Everything</button>
|
||||
<button type="button" data-action="cancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Restart Confirmation Modal -->
|
||||
<div id="confirmRestart" class="modal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="confirmRestartTitle">
|
||||
<div class="modal-backdrop"></div>
|
||||
<div class="modal-window">
|
||||
<h3 id="confirmRestartTitle">Restart Server?</h3>
|
||||
<p class="modal-text">This will stop the server process and attempt to restart it. Active requests will be interrupted. Make sure the app is managed by Docker, PM2, systemd, or another supervisor.</p>
|
||||
<ul class="modal-list">
|
||||
<li>Interrupts any in-flight API calls</li>
|
||||
<li>Server will exit and come back up</li>
|
||||
<li>UI will auto-reload when available</li>
|
||||
</ul>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="danger" data-action="confirm">Restart Now</button>
|
||||
<button type="button" data-action="cancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body></html>
|
||||
|
||||
+131
-38
@@ -124,13 +124,14 @@ function fillForm(data){
|
||||
}
|
||||
const excRaw = override.excludeCodecsRaw !== undefined ? override.excludeCodecsRaw : (merged.excludeCodecsRaw || '');
|
||||
let parsed = null; try { if(excRaw && /[\{\[]/.test(excRaw)) parsed = JSON.parse(excRaw); } catch {}
|
||||
let mode = 'all';
|
||||
let mode = 'none';
|
||||
if(parsed){
|
||||
const dv = parsed.excludeDV === true;
|
||||
const hdr = parsed.excludeHDR === true;
|
||||
if(dv && !hdr) mode = 'dv';
|
||||
if(!dv && !hdr) mode = 'none';
|
||||
else if(dv && hdr) mode = 'all';
|
||||
else if(dv && !hdr) mode = 'dv';
|
||||
else if(hdr && !dv) mode = 'hdr';
|
||||
else if(dv && hdr) mode = 'advanced';
|
||||
else mode = 'advanced';
|
||||
} else if(excRaw){
|
||||
if(/excludeDV":true/.test(excRaw) && !/excludeHDR":true/.test(excRaw)) mode='dv';
|
||||
@@ -167,25 +168,17 @@ function fillForm(data){
|
||||
['enableVixsrcProvider','adv_enableVixsrc'],
|
||||
['disableCache','adv_disableCache'],
|
||||
['enablePStreamApi','adv_enablePStream'],
|
||||
['showboxUseRotatingProxy','adv_showboxRotatingProxy'],
|
||||
['disableUrlValidation','adv_disableUrlValidation'],
|
||||
['disable4khdhubUrlValidation','adv_disable4khdhubValidation']
|
||||
];
|
||||
advBools.forEach(([cfgKey, elName])=>{ const el = f.elements[elName]; if(!el) return; const src = (override[cfgKey]!==undefined? override[cfgKey] : merged[cfgKey]); if(el.type==='checkbox'){ el.checked = !!src; } else { el.value = src? 'true':''; } });
|
||||
const advTexts = [
|
||||
['showboxCacheDir','adv_showboxCacheDir'],
|
||||
['showboxProxyPrimary','adv_showboxProxyPrimary'],
|
||||
['showboxProxyAlternate','adv_showboxProxyAlternate'],
|
||||
['xprimeProxyUrl','adv_xprimeProxyUrl'],
|
||||
['vidzeeProxyUrl','adv_vidzeeProxyUrl'],
|
||||
['vidsrcProxyUrl','adv_vidsrcProxyUrl'],
|
||||
['moviesmodProxyUrl','adv_moviesmodProxyUrl']
|
||||
['showboxCacheDir','adv_showboxCacheDir']
|
||||
];
|
||||
advTexts.forEach(([cfgKey, elName])=>{ const el = f.elements[elName]; if(!el) return; const val = override[cfgKey]!==undefined? override[cfgKey] : merged[cfgKey]; el.value = val || ''; });
|
||||
// Sync checkbox style advanced flags
|
||||
const advFlagMap = {
|
||||
adv_disableCache: 'disableCache',
|
||||
adv_showboxRotatingProxy: 'showboxUseRotatingProxy',
|
||||
adv_disableUrlValidation: 'disableUrlValidation',
|
||||
adv_disable4khdhubValidation: 'disable4khdhubUrlValidation'
|
||||
};
|
||||
@@ -234,7 +227,8 @@ async function save(){
|
||||
}
|
||||
const cPreset = document.querySelector('input[name="codecPreset"]:checked');
|
||||
if(cPreset){
|
||||
if(cPreset.value==='all') payload.excludeCodecsRaw = null;
|
||||
if(cPreset.value==='none') payload.excludeCodecsRaw = JSON.stringify({excludeDV:false,excludeHDR:false});
|
||||
else if(cPreset.value==='all') payload.excludeCodecsRaw = JSON.stringify({excludeDV:true,excludeHDR:true});
|
||||
else if(cPreset.value==='dv') payload.excludeCodecsRaw = JSON.stringify({excludeDV:true,excludeHDR:false});
|
||||
else if(cPreset.value==='hdr') payload.excludeCodecsRaw = JSON.stringify({excludeDV:false,excludeHDR:true});
|
||||
else if(cPreset.value==='advanced') {
|
||||
@@ -258,20 +252,11 @@ async function save(){
|
||||
adv_enableVixsrc:'enableVixsrcProvider',
|
||||
adv_disableCache:'disableCache',
|
||||
adv_enablePStream:'enablePStreamApi',
|
||||
adv_showboxRotatingProxy:'showboxUseRotatingProxy',
|
||||
adv_disableUrlValidation:'disableUrlValidation',
|
||||
adv_disable4khdhubValidation:'disable4khdhubUrlValidation'
|
||||
};
|
||||
Object.entries(boolMap).forEach(([formName,cfgKey])=>{ const el = f.elements[formName]; if(el) payload[cfgKey] = !!el.checked; });
|
||||
const textMap = {
|
||||
adv_showboxCacheDir:'showboxCacheDir',
|
||||
adv_showboxProxyPrimary:'showboxProxyPrimary',
|
||||
adv_showboxProxyAlternate:'showboxProxyAlternate',
|
||||
adv_xprimeProxyUrl:'xprimeProxyUrl',
|
||||
adv_vidzeeProxyUrl:'vidzeeProxyUrl',
|
||||
adv_vidsrcProxyUrl:'vidsrcProxyUrl',
|
||||
adv_moviesmodProxyUrl:'moviesmodProxyUrl'
|
||||
};
|
||||
const textMap = { adv_showboxCacheDir:'showboxCacheDir' };
|
||||
Object.entries(textMap).forEach(([formName,cfgKey])=>{ const el = f.elements[formName]; if(!el) return; const v = el.value.trim(); payload[cfgKey] = v? v : null; });
|
||||
const tmdbHidden = f.elements['tmdbApiKeysHidden'];
|
||||
if (tmdbHidden){
|
||||
@@ -288,18 +273,92 @@ async function save(){
|
||||
} catch(e){ setStatus(e.message,true); }
|
||||
}
|
||||
|
||||
function openConfirmReset(){
|
||||
const modal = document.getElementById('confirmReset');
|
||||
if(!modal) return Promise.resolve(false);
|
||||
// Respect persisted preference
|
||||
try {
|
||||
const skip = localStorage.getItem('ns_confirmReset_skip') === '1';
|
||||
if (skip) return Promise.resolve(true);
|
||||
} catch {}
|
||||
modal.classList.add('show');
|
||||
return new Promise(resolve => {
|
||||
const onClick = (e)=>{
|
||||
const action = e.target?.dataset?.action;
|
||||
if(action==='confirm'){
|
||||
try {
|
||||
const dontAsk = modal.querySelector('#confirmResetDontAsk')?.checked;
|
||||
if (dontAsk) localStorage.setItem('ns_confirmReset_skip','1');
|
||||
} catch {}
|
||||
cleanup(); resolve(true);
|
||||
}
|
||||
if(action==='cancel' || e.target===modal.querySelector('.modal-backdrop')){ cleanup(); resolve(false); }
|
||||
};
|
||||
function onKey(e){ if(e.key==='Escape'){ cleanup(); resolve(false); } }
|
||||
function cleanup(){ modal.classList.remove('show'); modal.removeEventListener('click', onClick); document.removeEventListener('keydown', onKey); }
|
||||
modal.addEventListener('click', onClick);
|
||||
document.addEventListener('keydown', onKey);
|
||||
});
|
||||
}
|
||||
|
||||
function openConfirmRestart(){
|
||||
const modal = document.getElementById('confirmRestart');
|
||||
if(!modal) return Promise.resolve(false);
|
||||
modal.classList.add('show');
|
||||
return new Promise(resolve => {
|
||||
const onClick = (e)=>{
|
||||
const action = e.target?.dataset?.action;
|
||||
if(action==='confirm'){ cleanup(); resolve(true); }
|
||||
if(action==='cancel' || e.target===modal.querySelector('.modal-backdrop')){ cleanup(); resolve(false); }
|
||||
};
|
||||
function onKey(e){ if(e.key==='Escape'){ cleanup(); resolve(false); } }
|
||||
function cleanup(){ modal.classList.remove('show'); modal.removeEventListener('click', onClick); document.removeEventListener('keydown', onKey); }
|
||||
modal.addEventListener('click', onClick);
|
||||
document.addEventListener('keydown', onKey);
|
||||
});
|
||||
}
|
||||
|
||||
async function clearAll(){
|
||||
if(!confirm('Remove ALL overrides?')) return;
|
||||
const payload = { port:null, defaultRegion:null, defaultProviders:[], minQualitiesRaw:null, excludeCodecsRaw:null, febboxCookies:null,
|
||||
enableXprimeProvider:null, enable4khdhubProvider:null, enableMoviesmodProvider:null, enableMp4hydraProvider:null, enableVidzeeProvider:null, enableVixsrcProvider:null,
|
||||
disableCache:null, enablePStreamApi:null, showboxUseRotatingProxy:null, disableUrlValidation:null, disable4khdhubUrlValidation:null,
|
||||
showboxCacheDir:null, showboxProxyPrimary:null, showboxProxyAlternate:null, xprimeProxyUrl:null, vidzeeProxyUrl:null, vidsrcProxyUrl:null, moviesmodProxyUrl:null };
|
||||
const confirmed = await openConfirmReset();
|
||||
if(!confirmed) return;
|
||||
// To truly reset to app defaults, we must:
|
||||
// - Explicitly clear arrays so env fallbacks don't persist
|
||||
// - Set boolean flags to their default values (rather than null)
|
||||
// - Null legacy single TMDB key
|
||||
const payload = {
|
||||
port: null,
|
||||
defaultRegion: null,
|
||||
defaultProviders: [],
|
||||
minQualitiesRaw: 'all',
|
||||
excludeCodecsRaw: JSON.stringify({excludeDV:false,excludeHDR:false}),
|
||||
// Explicit empty arrays ensure override wins over any prior env mirror
|
||||
febboxCookies: [],
|
||||
tmdbApiKeys: [],
|
||||
tmdbApiKey: null,
|
||||
// Provider enable flags default to true
|
||||
enableShowboxProvider: true,
|
||||
enableXprimeProvider: true,
|
||||
enable4khdhubProvider: true,
|
||||
enableMoviesmodProvider: true,
|
||||
enableMp4hydraProvider: true,
|
||||
enableVidzeeProvider: true,
|
||||
enableVixsrcProvider: true,
|
||||
// Other defaults
|
||||
disableCache: false,
|
||||
enablePStreamApi: true,
|
||||
disableUrlValidation: false,
|
||||
disable4khdhubUrlValidation: false,
|
||||
showboxCacheDir: null
|
||||
};
|
||||
try {
|
||||
const r = await fetch('/api/config',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});
|
||||
const js = await r.json();
|
||||
if(!js.success) throw new Error(js.error||'Clear failed');
|
||||
setStatus('Overrides cleared');
|
||||
setStatus('All settings reset to defaults');
|
||||
fillForm(js);
|
||||
// Re-render provider matrix immediately so toggles reflect defaults without page refresh
|
||||
try { renderProviderMatrix(js.merged); } catch {}
|
||||
validate();
|
||||
} catch(e){ setStatus(e.message,true); }
|
||||
}
|
||||
|
||||
@@ -443,6 +502,30 @@ document.addEventListener('DOMContentLoaded', ()=> {
|
||||
location.replace('/?loggedOut=1');
|
||||
});
|
||||
}
|
||||
const restartBtn = document.getElementById('restartBtn');
|
||||
if(restartBtn){
|
||||
restartBtn.addEventListener('click', async ()=>{
|
||||
const ok = await openConfirmRestart();
|
||||
if(!ok) return;
|
||||
setStatus('Restarting server…');
|
||||
try {
|
||||
const r = await fetch('/api/restart',{ method:'POST' });
|
||||
if(!r.ok){ const t = await r.text().catch(()=>r.statusText); throw new Error(t||('HTTP '+r.status)); }
|
||||
// Server will shut down soon; start polling until it comes back
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
const start = Date.now();
|
||||
const timeoutMs = 60_000;
|
||||
(async function poll(){
|
||||
try {
|
||||
const pr = await fetch('/api/health',{ cache:'no-store' });
|
||||
if(pr.ok){ location.reload(); return; }
|
||||
} catch {}
|
||||
if(Date.now()-start > timeoutMs){ setStatus('Restart timed out', true); return; }
|
||||
setTimeout(poll, 1000);
|
||||
})();
|
||||
} catch(e){ setStatus('Restart failed: '+e.message, true); }
|
||||
});
|
||||
}
|
||||
reload();
|
||||
const h = location.hash.slice(1);
|
||||
navTo(h||'panel-core');
|
||||
@@ -513,15 +596,25 @@ async function renderProviderMatrix(merged){
|
||||
if(!merged){ try { const data = await fetchConfig(); merged = data.merged||{}; } catch { return; } }
|
||||
const container = document.getElementById('providerMatrix'); if(!container) return;
|
||||
const defaults = (merged.defaultProviders||[]).map(s=>s.toLowerCase());
|
||||
const providers = [
|
||||
['showbox','enableShowboxProvider'],
|
||||
['4khdhub','enable4khdhubProvider'],
|
||||
['moviesmod','enableMoviesmodProvider'],
|
||||
['mp4hydra','enableMp4hydraProvider'],
|
||||
['vidzee','enableVidzeeProvider'],
|
||||
['vixsrc','enableVixsrcProvider'],
|
||||
['xprime','enableXprimeProvider']
|
||||
];
|
||||
|
||||
// Fetch available providers from API
|
||||
let availableProviders = [];
|
||||
try {
|
||||
const providersResponse = await fetch('/api/providers');
|
||||
if (providersResponse.ok) {
|
||||
const providersData = await providersResponse.json();
|
||||
availableProviders = providersData.providers || [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to fetch providers list:', e.message);
|
||||
}
|
||||
|
||||
// Create provider list with enable flags
|
||||
const providers = availableProviders.map(p => {
|
||||
const flag = `enable${p.name.charAt(0).toUpperCase() + p.name.slice(1)}Provider`;
|
||||
return [p.name, flag];
|
||||
});
|
||||
|
||||
container.innerHTML = providers.map(([name,flag])=>{
|
||||
const enabled = merged[flag] !== false;
|
||||
const isDefault = defaults.includes(name);
|
||||
|
||||
@@ -129,6 +129,9 @@ body {
|
||||
.nav-item.theme-toggle { margin-top: 12px; }
|
||||
.nav-item#logoutBtn { border:1px solid var(--danger); color: var(--danger); font-weight:700; }
|
||||
.nav-item#logoutBtn:hover { background: var(--danger); color:#fff; }
|
||||
.nav-item#restartBtn { border:1px solid var(--danger); color: var(--danger); font-weight:700; }
|
||||
.nav-item#restartBtn:hover { background: var(--danger); color:#fff; }
|
||||
.nav-sep { height: 1px; background: var(--border); margin: 6px 0 8px; opacity: .6; margin-bottom: 1rem; margin-top: 1rem;}
|
||||
.grow { flex-grow: 1; }
|
||||
.side-footer { font-size: .65rem; opacity: .6; line-height: 1.4; text-align: center;}
|
||||
.main {
|
||||
@@ -364,3 +367,15 @@ input.invalid, textarea.invalid { border-color: var(--danger)!important; animati
|
||||
.providers-table .prov-row { border:1px solid var(--border); }
|
||||
.prov-row strong { font-size:.7rem; letter-spacing:.5px; }
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; z-index: 100; }
|
||||
.modal.show { display: flex; }
|
||||
.modal .modal-backdrop { position: absolute; inset:0; background: rgba(0,0,0,.45); backdrop-filter: blur(2px); }
|
||||
.modal .modal-window { position: relative; z-index: 1; width: min(560px, 94vw); background: var(--bg-alt); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 18px 20px; }
|
||||
.modal .modal-window h3 { margin: 0 0 8px; font-size: 1.05rem; letter-spacing: .5px; }
|
||||
.modal .modal-text { margin: 0 0 10px; font-size: .85rem; color: var(--text-dim); }
|
||||
.modal .modal-list { margin: 0 0 14px 18px; padding: 0; color: var(--text-dim); font-size: .8rem; }
|
||||
.modal .modal-opt { display:flex; align-items:center; gap:8px; margin: 0 0 12px; font-size:.8rem; color: var(--text-dim); }
|
||||
.modal .modal-opt input { accent-color: var(--primary); }
|
||||
.modal .modal-actions { display: flex; gap: 10px; justify-content: flex-end; }
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1758225404146
|
||||
+56
-44
@@ -52,6 +52,19 @@ function writeOverrideFile(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
function getProviderNames() {
|
||||
try {
|
||||
const providersDir = path.join(process.cwd(), 'providers');
|
||||
if (!fs.existsSync(providersDir)) return [];
|
||||
return fs.readdirSync(providersDir)
|
||||
.filter(file => file.endsWith('.js') && file !== 'registry.js' && file !== 'vidsrcextractor.js')
|
||||
.map(file => path.parse(file).name.toLowerCase());
|
||||
} catch (e) {
|
||||
console.warn('[config] Failed to scan providers directory:', e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeConfig(base) {
|
||||
const cfg = { ...base };
|
||||
// Marker: if user explicitly provided an empty array for tmdbApiKeys in override file we should not resurrect legacy key
|
||||
@@ -78,23 +91,26 @@ function normalizeConfig(base) {
|
||||
}
|
||||
// Ensure dedupe + trim
|
||||
cfg.tmdbApiKeys = Array.from(new Set(cfg.tmdbApiKeys.map(k=>String(k).trim()).filter(Boolean)));
|
||||
// Booleans normalization
|
||||
const boolKeys = [
|
||||
'enableShowboxProvider','enableXprimeProvider','enable4khdhubProvider','enableMoviesmodProvider','enableMp4hydraProvider','enableVidzeeProvider','enableVixsrcProvider',
|
||||
'disableCache','enablePStreamApi','showboxUseRotatingProxy','disableUrlValidation','disable4khdhubUrlValidation'
|
||||
];
|
||||
// Do not expose legacy single-key field in merged config output
|
||||
delete cfg.tmdbApiKey;
|
||||
|
||||
// Dynamic provider enable flags
|
||||
const providerNames = getProviderNames();
|
||||
const boolKeys = providerNames.map(name => `enable${name.charAt(0).toUpperCase() + name.slice(1)}Provider`);
|
||||
boolKeys.push('disableCache', 'enablePStreamApi', 'disableUrlValidation', 'disable4khdhubUrlValidation');
|
||||
|
||||
boolKeys.forEach(k=>{ if (cfg[k] === 'true') cfg[k] = true; else if (cfg[k] === 'false') cfg[k] = false; });
|
||||
// Default values if undefined
|
||||
if (cfg.enableShowboxProvider === undefined) cfg.enableShowboxProvider = true;
|
||||
if (cfg.enableXprimeProvider === undefined) cfg.enableXprimeProvider = true;
|
||||
if (cfg.enable4khdhubProvider === undefined) cfg.enable4khdhubProvider = true;
|
||||
if (cfg.enableMoviesmodProvider === undefined) cfg.enableMoviesmodProvider = true;
|
||||
if (cfg.enableMp4hydraProvider === undefined) cfg.enableMp4hydraProvider = true;
|
||||
if (cfg.enableVidzeeProvider === undefined) cfg.enableVidzeeProvider = true;
|
||||
if (cfg.enableVixsrcProvider === undefined) cfg.enableVixsrcProvider = true;
|
||||
|
||||
// Set defaults for provider enable flags
|
||||
providerNames.forEach(name => {
|
||||
const flag = `enable${name.charAt(0).toUpperCase() + name.slice(1)}Provider`;
|
||||
if (cfg[flag] === undefined) cfg[flag] = true; // Default to enabled
|
||||
});
|
||||
|
||||
// Default values for other flags
|
||||
if (cfg.disableCache === undefined) cfg.disableCache = false;
|
||||
if (cfg.enablePStreamApi === undefined) cfg.enablePStreamApi = true;
|
||||
if (cfg.showboxUseRotatingProxy === undefined) cfg.showboxUseRotatingProxy = false;
|
||||
// Proxy features removed; always use direct connections
|
||||
if (cfg.disableUrlValidation === undefined) cfg.disableUrlValidation = false;
|
||||
if (cfg.disable4khdhubUrlValidation === undefined) cfg.disable4khdhubUrlValidation = false;
|
||||
return cfg;
|
||||
@@ -112,14 +128,15 @@ function applyConfigToEnv(cfg){
|
||||
if (cfg.febboxCookies && cfg.febboxCookies.length) process.env.FEBBOX_COOKIES = cfg.febboxCookies.join(',');
|
||||
else delete process.env.FEBBOX_COOKIES;
|
||||
if (cfg.defaultRegion) process.env.DEFAULT_REGION = cfg.defaultRegion; else delete process.env.DEFAULT_REGION;
|
||||
// Provider enable flags
|
||||
process.env.ENABLE_SHOWBOX_PROVIDER = cfg.enableShowboxProvider ? 'true':'false';
|
||||
process.env.ENABLE_XPRIME_PROVIDER = cfg.enableXprimeProvider ? 'true':'false';
|
||||
process.env.ENABLE_4KHDHUB_PROVIDER = cfg.enable4khdhubProvider ? 'true':'false';
|
||||
process.env.ENABLE_MOVIESMOD_PROVIDER = cfg.enableMoviesmodProvider ? 'true':'false';
|
||||
process.env.ENABLE_MP4HYDRA_PROVIDER = cfg.enableMp4hydraProvider ? 'true':'false';
|
||||
process.env.ENABLE_VIDZEE_PROVIDER = cfg.enableVidzeeProvider ? 'true':'false';
|
||||
process.env.ENABLE_VIXSRC_PROVIDER = cfg.enableVixsrcProvider ? 'true':'false';
|
||||
|
||||
// Dynamic provider enable flags
|
||||
const providerNames = getProviderNames();
|
||||
providerNames.forEach(name => {
|
||||
const flag = `enable${name.charAt(0).toUpperCase() + name.slice(1)}Provider`;
|
||||
const envName = `ENABLE_${name.toUpperCase()}_PROVIDER`;
|
||||
process.env[envName] = cfg[flag] ? 'true' : 'false';
|
||||
});
|
||||
|
||||
// Caching / validation / PStream
|
||||
process.env.DISABLE_CACHE = cfg.disableCache ? 'true':'false';
|
||||
process.env.ENABLE_PSTREAM_API = cfg.enablePStreamApi ? 'true':'false';
|
||||
@@ -127,14 +144,14 @@ function applyConfigToEnv(cfg){
|
||||
process.env.DISABLE_4KHDHUB_URL_VALIDATION = cfg.disable4khdhubUrlValidation ? 'true':'false';
|
||||
// Showbox specific
|
||||
if (cfg.showboxCacheDir) process.env.SHOWBOX_CACHE_DIR = cfg.showboxCacheDir; else delete process.env.SHOWBOX_CACHE_DIR;
|
||||
process.env.SHOWBOX_USE_ROTATING_PROXY = cfg.showboxUseRotatingProxy ? 'true':'false';
|
||||
if (cfg.showboxProxyPrimary) process.env.SHOWBOX_PROXY_URL_VALUE = cfg.showboxProxyPrimary; else delete process.env.SHOWBOX_PROXY_URL_VALUE;
|
||||
if (cfg.showboxProxyAlternate) process.env.SHOWBOX_PROXY_URL_ALTERNATE = cfg.showboxProxyAlternate; else delete process.env.SHOWBOX_PROXY_URL_ALTERNATE;
|
||||
// Other provider proxies
|
||||
if (cfg.xprimeProxyUrl) process.env.XPRIME_PROXY_URL = cfg.xprimeProxyUrl; else delete process.env.XPRIME_PROXY_URL;
|
||||
if (cfg.vidzeeProxyUrl) process.env.VIDZEE_PROXY_URL = cfg.vidzeeProxyUrl; else delete process.env.VIDZEE_PROXY_URL;
|
||||
if (cfg.vidsrcProxyUrl) process.env.VIDSRC_PROXY_URL = cfg.vidsrcProxyUrl; else delete process.env.VIDSRC_PROXY_URL;
|
||||
if (cfg.moviesmodProxyUrl) process.env.MOVIESMOD_PROXY_URL = cfg.moviesmodProxyUrl; else delete process.env.MOVIESMOD_PROXY_URL;
|
||||
// Proxy settings removed; ensure legacy envs are cleared
|
||||
delete process.env.SHOWBOX_USE_ROTATING_PROXY;
|
||||
delete process.env.SHOWBOX_PROXY_URL_VALUE;
|
||||
delete process.env.SHOWBOX_PROXY_URL_ALTERNATE;
|
||||
delete process.env.XPRIME_PROXY_URL;
|
||||
delete process.env.VIDZEE_PROXY_URL;
|
||||
delete process.env.VIDSRC_PROXY_URL;
|
||||
delete process.env.MOVIESMOD_PROXY_URL;
|
||||
if (cfg.defaultRegion) process.env.FEBBOX_REGION = cfg.defaultRegion; // alias
|
||||
}
|
||||
|
||||
@@ -150,26 +167,21 @@ function loadConfig() {
|
||||
tmdbApiKeys: parseJsonMaybe(process.env.TMDB_API_KEYS) || null,
|
||||
febboxCookies: parseCookies(process.env.FEBBOX_COOKIES),
|
||||
// Extended advanced config (may not exist in env)
|
||||
enableShowboxProvider: process.env.ENABLE_SHOWBOX_PROVIDER,
|
||||
enableXprimeProvider: process.env.ENABLE_XPRIME_PROVIDER,
|
||||
enable4khdhubProvider: process.env.ENABLE_4KHDHUB_PROVIDER,
|
||||
enableMoviesmodProvider: process.env.ENABLE_MOVIESMOD_PROVIDER,
|
||||
enableMp4hydraProvider: process.env.ENABLE_MP4HYDRA_PROVIDER,
|
||||
enableVidzeeProvider: process.env.ENABLE_VIDZEE_PROVIDER,
|
||||
enableVixsrcProvider: process.env.ENABLE_VIXSRC_PROVIDER,
|
||||
disableCache: process.env.DISABLE_CACHE,
|
||||
enablePStreamApi: process.env.ENABLE_PSTREAM_API,
|
||||
showboxUseRotatingProxy: process.env.SHOWBOX_USE_ROTATING_PROXY,
|
||||
showboxCacheDir: process.env.SHOWBOX_CACHE_DIR || null,
|
||||
showboxProxyPrimary: process.env.SHOWBOX_PROXY_URL_VALUE || null,
|
||||
showboxProxyAlternate: process.env.SHOWBOX_PROXY_URL_ALTERNATE || null,
|
||||
xprimeProxyUrl: process.env.XPRIME_PROXY_URL || null,
|
||||
vidzeeProxyUrl: process.env.VIDZEE_PROXY_URL || null,
|
||||
vidsrcProxyUrl: process.env.VIDSRC_PROXY_URL || null,
|
||||
moviesmodProxyUrl: process.env.MOVIESMOD_PROXY_URL || null,
|
||||
// Proxy/env paths removed; always direct connections
|
||||
disableUrlValidation: process.env.DISABLE_URL_VALIDATION,
|
||||
disable4khdhubUrlValidation: process.env.DISABLE_4KHDHUB_URL_VALIDATION
|
||||
};
|
||||
|
||||
// Dynamic provider enable flags from env
|
||||
const providerNames = getProviderNames();
|
||||
providerNames.forEach(name => {
|
||||
const envName = `ENABLE_${name.toUpperCase()}_PROVIDER`;
|
||||
envCfg[`enable${name.charAt(0).toUpperCase() + name.slice(1)}Provider`] = process.env[envName];
|
||||
});
|
||||
|
||||
const override = readOverrideFile();
|
||||
const merged = { ...envCfg, ...override };
|
||||
const normalized = normalizeConfig(merged);
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
class JsUnpacker {
|
||||
static Unbase = class {
|
||||
constructor(radix) {
|
||||
this.ALPHABET_62 =
|
||||
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
this.ALPHABET_95 =
|
||||
' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
|
||||
this.alphabet = null;
|
||||
this.dictionary = {};
|
||||
this.radix = radix;
|
||||
|
||||
if (radix > 36) {
|
||||
if (radix < 62) {
|
||||
this.alphabet = this.ALPHABET_62.substring(0, radix);
|
||||
} else if (radix > 62 && radix < 95) {
|
||||
this.alphabet = this.ALPHABET_95.substring(0, radix);
|
||||
} else if (radix === 62) {
|
||||
this.alphabet = this.ALPHABET_62;
|
||||
} else if (radix === 95) {
|
||||
this.alphabet = this.ALPHABET_95;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.alphabet.length; i++) {
|
||||
this.dictionary[this.alphabet[i]] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unbase(str) {
|
||||
let ret = 0;
|
||||
|
||||
if (this.alphabet === null) {
|
||||
ret = parseInt(str, this.radix);
|
||||
} else {
|
||||
const tmp = str.split('').reverse().join('');
|
||||
for (let i = 0; i < tmp.length; i++) {
|
||||
ret += Math.pow(this.radix, i) * this.dictionary[tmp[i]];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
constructor(packedJS) {
|
||||
this.packedJS = packedJS;
|
||||
}
|
||||
|
||||
detect() {
|
||||
const js = this.packedJS.replace(/\s/g, '');
|
||||
const regex = /eval\(function\(p,a,c,k,e,(?:r|d)/;
|
||||
return regex.test(js);
|
||||
}
|
||||
|
||||
unpack() {
|
||||
let js = this.packedJS;
|
||||
try {
|
||||
const regex =
|
||||
/}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)/s;
|
||||
const match = js.match(regex);
|
||||
if (match && match.length === 5) {
|
||||
let payload = match[1].replace(/\\'/g, "'");
|
||||
const radixStr = match[2];
|
||||
const countStr = match[3];
|
||||
const symtab = match[4].split('|');
|
||||
|
||||
let radix = 36;
|
||||
let count = 0;
|
||||
try {
|
||||
radix = parseInt(radixStr);
|
||||
} catch (e) {}
|
||||
try {
|
||||
count = parseInt(countStr);
|
||||
} catch (e) {}
|
||||
|
||||
if (symtab.length !== count) {
|
||||
throw new Error('Unknown p.a.c.k.e.r. encoding');
|
||||
}
|
||||
|
||||
const unbase = new this.constructor.Unbase(radix);
|
||||
const wordRegex = /\b\w+\b/g;
|
||||
let decoded = payload;
|
||||
let replaceOffset = 0;
|
||||
let wordMatch;
|
||||
|
||||
while ((wordMatch = wordRegex.exec(payload)) !== null) {
|
||||
const word = wordMatch[0];
|
||||
const x = unbase.unbase(word);
|
||||
let value = null;
|
||||
if (x < symtab.length) {
|
||||
value = symtab[x];
|
||||
}
|
||||
|
||||
if (value && value.length > 0) {
|
||||
const start = wordMatch.index + replaceOffset;
|
||||
const end = start + word.length;
|
||||
decoded =
|
||||
decoded.substring(0, start) +
|
||||
value +
|
||||
decoded.substring(end);
|
||||
replaceOffset += value.length - word.length;
|
||||
}
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// CommonJS exports
|
||||
module.exports = JsUnpacker;
|
||||
+14
-1
@@ -2,5 +2,18 @@
|
||||
"defaultProviders": [],
|
||||
"configVersion": 1,
|
||||
"tmdbApiKeys": [],
|
||||
"enableShowboxProvider": true
|
||||
"enableShowboxProvider": true,
|
||||
"febboxCookies": [],
|
||||
"enableXprimeProvider": true,
|
||||
"enable4khdhubProvider": true,
|
||||
"enableMoviesmodProvider": true,
|
||||
"enableMp4hydraProvider": true,
|
||||
"enableVidzeeProvider": true,
|
||||
"enableVixsrcProvider": true,
|
||||
"disableCache": false,
|
||||
"enablePStreamApi": true,
|
||||
"disableUrlValidation": false,
|
||||
"disable4khdhubUrlValidation": false,
|
||||
"excludeCodecsRaw": "{\"excludeDV\":false,\"excludeHDR\":false}",
|
||||
"minQualitiesRaw": "all"
|
||||
}
|
||||
Reference in New Issue
Block a user