Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,18 @@ See [CONTRIBUTING.md](https://github.com/omss-spec/omss-spec/blob/main/CONTRIBUT

---

## Star History

<a href="https://www.star-history.com/#cinepro-org/core&type=date&legend=top-left">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=cinepro-org/core&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=cinepro-org/core&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=cinepro-org/core&type=date&legend=top-left" />
</picture>
</a>

---

## 🔒 Legal Notice

CinePro Core is designed for **personal and home use only**. Users are responsible for ensuring compliance with applicable laws and terms of service for streaming sources. This software does not host, store, or distribute any copyrighted content. [github](https://github.com/cinepro-org)
Expand Down
340 changes: 319 additions & 21 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"clean": "npx rimraf dist"
},
"dependencies": {
"@omss/framework": "^1.1.11",
"@omss/framework": "^1.1.13",
"cheerio": "^1.2.0",
"crypto-js": "^4.2.0",
"dotenv": "^16.4.5"
},
Expand Down
5 changes: 4 additions & 1 deletion src/providers/02moviedownloader/02moviedownloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@ export class MovieDownloader extends BaseProvider {
}

sources.push({
url: this.createProxyUrl(stream.url, this.HEADERS),
url: this.createProxyUrl(
stream.url,
stream.url.includes('pixeldra') ? {} : this.HEADERS
),
type: inferredType as SourceType,
quality: height ? height.toString() : stream.quality,
audioTracks: [
Expand Down
132 changes: 132 additions & 0 deletions src/providers/icefy/icefy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { BaseProvider } from '@omss/framework';
import type {
ProviderCapabilities,
ProviderMediaObject,
ProviderResult
} from '@omss/framework';
import axios from 'axios';

// Icefy is behind Cloudflare and i can't fake the token..... idk how to get this running --> disabled
export class IcefyProvider extends BaseProvider {
readonly id = 'Icefy';
readonly name = 'Icefy';
readonly enabled = false;
readonly BASE_URL = 'https://streams.icefy.top';
readonly HEADERS = {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150 Safari/537.36',
Accept: 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'en-US,en;q=0.9',
Cookie: 'cf_clearance=uPAAkmZ3oIhWibNi0dAUILBj6DHl1LJdY2CdsKnX0rI-1700000000-0-150; _ga=GA1.2.123456789.1700000000; _gid=GA1.2.987654321.1700000000',
Referer: this.BASE_URL,
Origin: this.BASE_URL
};

readonly capabilities: ProviderCapabilities = {
supportedContentTypes: ['movies', 'tv']
};

/**
* Fetch movie sources
*/
async getMovieSources(media: ProviderMediaObject): Promise<ProviderResult> {
return this.getSources(media);
}

/**
* Fetch TV episode sources
*/
async getTVSources(media: ProviderMediaObject): Promise<ProviderResult> {
return this.getSources(media);
}

/**
* Main scraping logic
*/
private async getSources(
media: ProviderMediaObject
): Promise<ProviderResult> {
try {
const pageUrl = this.puildPlaylistUrl(media);

const result: ProviderResult = {
sources: [
{
url: this.createProxyUrl(pageUrl, this.HEADERS),
quality: '1080p',
type: 'hls',
audioTracks: [
{
label: 'English',
language: 'eng'
}
],
provider: {
name: this.name,
id: this.id
}
}
],
subtitles: [],
diagnostics: []
};

return result;
} catch (error) {
return this.emptyResult(
error instanceof Error
? error.message
: 'Unknown provider error',
media
);
}
}

/**
* Build page URL based on media type
*/
private puildPlaylistUrl(media: ProviderMediaObject): string {
if (media.type === 'movie') {
return `${this.BASE_URL}/movie/${media.tmdbId}/bump/master.m3u8`;
} else if (media.type === 'tv') {
return `${this.BASE_URL}/tv/${media.tmdbId}/${media.s}/${media.e}/bump/master.m3u8`;
}
throw new Error('Unsupported media type');
}

/**
* Return empty result with diagnostic
*/
private emptyResult(
message: string,
media: ProviderMediaObject
): ProviderResult {
return {
sources: [],
subtitles: [],
diagnostics: [
{
code: 'PROVIDER_ERROR',
message: `${this.name}: ${message}`,
field: '',
severity: 'error'
}
]
};
}

/**
* Health check
*/
async healthCheck(): Promise<boolean> {
try {
const response = await axios.head(this.BASE_URL, {
timeout: 5000,
headers: this.HEADERS
});
return response.status === 200;
} catch {
return false;
}
}
}
4 changes: 4 additions & 0 deletions src/providers/icefy/icefy.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IcefyResponse {
success: boolean;
url: string;
}
2 changes: 1 addition & 1 deletion src/providers/vidrock/vidrock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class VidRockProvider extends BaseProvider {

let finalUrl: string;

if (stream.url.startsWith('https://cdn.vidrock.store/')) {
if (stream.url.includes('hls2.vdrk.site')) {
const secondData = (await this.fetchPage(
stream.url
)) as unknown as VidrockCDN[];
Expand Down
6 changes: 4 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { OMSSServer } from '@omss/framework';
import 'dotenv/config';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import { knownThirdPartyProxies } from './config.js';
import { knownThirdPartyProxies } from './thirdPartyProxies.js';
import { streamPatterns } from './streamPatterns.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand Down Expand Up @@ -39,7 +40,8 @@ async function main() {

// Third Party Proxy removal
proxyConfig: {
knownThirdPartyProxies: knownThirdPartyProxies
knownThirdPartyProxies: knownThirdPartyProxies,
streamPatterns
}
});

Expand Down
7 changes: 7 additions & 0 deletions src/streamPatterns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// These regex patterns are used that the proxy can identify which urls should be streamed.
// by default the most common video files are included in the @omss/framework

export const streamPatterns: RegExp[] = [
/pixeldrain|pixeldra\.in/,
/hub.raj.lat/
];
1 change: 1 addition & 0 deletions src/config.ts → src/thirdPartyProxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const knownThirdPartyProxies: Record<string, RegExp[]> = {

'*': [
/^https:\/\/[^/]+\.workers\.dev\/((?:https?:\/\/)?[^/]+\/file2\/.+)$/, // any workers.dev/[domain]/file2/[content] capturer
/^https:\/\/.+?\.workers\.dev\/((?:https?:\/\/).+)$/, // any [subdomain].workers.dev/[https://..... link] capturer
/\/proxy\/(.+)$/, // Generic /proxy/encoded
/\/m3u8-proxy\?url=(.+?)(?:&|$)/, // m3u8-proxy?url=
/\/api\/[^/]+\/proxy\?url=(.+)$/, // /api/*/proxy?url=
Expand Down
102 changes: 102 additions & 0 deletions src/utils/jsunpack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// jsunpack.ts

class Unbase {
private readonly ALPHABET_62 =
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
private readonly ALPHABET_95 =
' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';

private radix: number;
private alphabet: string | null = null;
private dictionary: Record<string, number> = {};

constructor(radix: number) {
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;
}
if (this.alphabet) {
for (let i = 0; i < this.alphabet.length; i++) {
this.dictionary[this.alphabet[i]] = i;
}
}
}
}

unbase(str: string): number {
if (this.alphabet === null) {
return parseInt(str, this.radix);
}
const tmp = str.split('').reverse().join('');
let ret = 0;
for (let i = 0; i < tmp.length; i++) {
ret += Math.pow(this.radix, i) * (this.dictionary[tmp[i]] ?? 0);
}
return ret;
}
}

export default class JsUnpacker {
private packedJS: string;

constructor(packedJS: string) {
this.packedJS = packedJS;
}

// returns true if the input looks like p.a.c.k.e.r encoded js
detect(): boolean {
const js = this.packedJS.replace(/\s/g, '');
return /eval\(function\(p,a,c,k,e,(?:r|d)/.test(js);
}

// decodes packed js back to readable source
unpack(): string | null {
const js = this.packedJS;
try {
const regex =
/}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)/s;
const match = js.match(regex);
if (!match || match.length !== 5) return null;

let payload = match[1].replace(/\\'/g, "'");
const radix = parseInt(match[2]) || 36;
const count = parseInt(match[3]) || 0;
const symtab = match[4].split('|');

if (symtab.length !== count) return null;

const unbase = new Unbase(radix);
const wordRegex = /\b\w+\b/g;
let decoded = payload;
let replaceOffset = 0;
let wordMatch: RegExpExecArray | null;

while ((wordMatch = wordRegex.exec(payload)) !== null) {
const word = wordMatch[0];
const x = unbase.unbase(word);
const value = x < symtab.length ? symtab[x] : null;

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 {
return null;
}
}
}