Spaces:
Paused
Paused
Upload 9 files
Browse files- episodes.js +87 -57
- main.js +41 -15
- package-lock.json +0 -0
- package.json +23 -22
- rama_series.js +118 -30
- streams.js +69 -61
episodes.js
CHANGED
|
@@ -1,57 +1,87 @@
|
|
| 1 |
-
import axios from 'axios';
|
| 2 |
-
import cloudscraper from 'cloudscraper';
|
| 3 |
-
import * as cheerio from 'cheerio';
|
| 4 |
-
import
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
const
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
meta
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import axios from 'axios';
|
| 2 |
+
import cloudscraper from 'cloudscraper';
|
| 3 |
+
import * as cheerio from 'cheerio';
|
| 4 |
+
import { getStream, getEpisodes } from './streams.js'; // Importa sia getStream che getEpisodes
|
| 5 |
+
|
| 6 |
+
// Lista di User-Agent
|
| 7 |
+
const userAgents = [
|
| 8 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.3',
|
| 9 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0',
|
| 10 |
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Safari/605.1.15'
|
| 11 |
+
];
|
| 12 |
+
|
| 13 |
+
// Funzione per ottenere headers casuali
|
| 14 |
+
function getRandomHeaders() {
|
| 15 |
+
const userAgent = userAgents[Math.floor(Math.random() * userAgents.length)];
|
| 16 |
+
return {
|
| 17 |
+
'User-Agent': userAgent,
|
| 18 |
+
'Referer': 'https://ramaorientalfansub.tv/paese/corea-del-sud/',
|
| 19 |
+
'Accept-Language': 'en-US,en;q=0.9'
|
| 20 |
+
};
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
// Istanza di Axios con headers casuali
|
| 24 |
+
const axiosInstance = axios.create({
|
| 25 |
+
headers: getRandomHeaders()
|
| 26 |
+
});
|
| 27 |
+
export { axiosInstance, axios, cloudscraper, cheerio };
|
| 28 |
+
// Cache per i metadati delle serie
|
| 29 |
+
const metaCache = new Map();
|
| 30 |
+
|
| 31 |
+
async function fetchWithCloudscraper(url) {
|
| 32 |
+
try {
|
| 33 |
+
const data = await cloudscraper.get({
|
| 34 |
+
uri: url,
|
| 35 |
+
headers: getRandomHeaders() // Aggiungi headers casuali anche qui
|
| 36 |
+
});
|
| 37 |
+
return data;
|
| 38 |
+
} catch (error) {
|
| 39 |
+
console.error("Errore con Cloudscraper:", error);
|
| 40 |
+
return null;
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
async function getMeta(id) {
|
| 45 |
+
const meta = { id, type: 'series', name: '', poster: '', episodes: [] };
|
| 46 |
+
const cleanId = id.replace(/,/g, '').replace(/\s+/g, '-').toLowerCase();
|
| 47 |
+
const baseId = cleanId.replace(/-\d{4}$/, '');
|
| 48 |
+
const seriesLink = `https://ramaorientalfansub.tv/drama/${baseId}/`;
|
| 49 |
+
// Verifica se i metadati sono in cache
|
| 50 |
+
if (metaCache.has(id)) {
|
| 51 |
+
console.log(`Caricamento metadati dalla cache per ${id}`);
|
| 52 |
+
return { meta: metaCache.get(id) }; // Restituisci direttamente i metadati dalla cache
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
try {
|
| 56 |
+
const data = await fetchWithCloudscraper(seriesLink);
|
| 57 |
+
if (!data) {
|
| 58 |
+
console.warn(`Nessun dato ricevuto per ${seriesLink}`);
|
| 59 |
+
return { meta };
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
const $ = cheerio.load(data);
|
| 63 |
+
// Carica i metadati (name, poster, description)
|
| 64 |
+
meta.name = $('a.text-accent').text().trim();
|
| 65 |
+
meta.poster = $('img.wp-post-image').attr('src');
|
| 66 |
+
let description = $('div.font-light > div:nth-child(1)').text().trim(); // Recupera la descrizione
|
| 67 |
+
// Aggiungi il tag alla descrizione se presente
|
| 68 |
+
if (meta.extra && meta.extra.tag) {
|
| 69 |
+
description += ` [${meta.extra.tag.toUpperCase()}]`;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
meta.description = description; // Assegna la descrizione modificata a meta.description
|
| 73 |
+
// Carica gli episodi
|
| 74 |
+
meta.episodes = await getEpisodes(seriesLink, $); // Passa l'oggetto cheerio
|
| 75 |
+
console.log('Episodi trovati:', meta.episodes);
|
| 76 |
+
// Salva i metadati nella cache (CON gli episodi)
|
| 77 |
+
metaCache.set(id, meta);
|
| 78 |
+
console.log(`Metadati salvati nella cache per ${id}`);
|
| 79 |
+
} catch (error) {
|
| 80 |
+
console.error('Errore nel caricamento dei dettagli della serie:', error);
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
console.log('Meta finale:', meta);
|
| 84 |
+
return { meta };
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
export { getMeta };
|
main.js
CHANGED
|
@@ -2,7 +2,6 @@ import pkg from 'stremio-addon-sdk';
|
|
| 2 |
import seriesCatalog from './rama_series.js';
|
| 3 |
import filmsCatalog from './rama_films.js';
|
| 4 |
import { getMeta } from './episodes.js';
|
| 5 |
-
import { getEpisodes } from './streams.js';
|
| 6 |
|
| 7 |
const { addonBuilder, serveHTTP } = pkg;
|
| 8 |
|
|
@@ -16,46 +15,59 @@ const manifest = {
|
|
| 16 |
"type": "series",
|
| 17 |
"id": "rama_series",
|
| 18 |
"name": "Serie Coreane",
|
| 19 |
-
"extra": [{"name": "skip"}]
|
| 20 |
},
|
| 21 |
{
|
| 22 |
"type": "movie",
|
| 23 |
"id": "rama_films",
|
| 24 |
"name": "Film Coreani",
|
| 25 |
-
"extra": [{"name": "skip"}]
|
| 26 |
}
|
| 27 |
],
|
| 28 |
-
"resources": ["catalog", "meta", "stream"],
|
| 29 |
"types": ["series", "movie"],
|
| 30 |
"logo": "https://ramaorientalfansub.tv/wp-content/uploads/2023/10/cropped-Logo-1.png",
|
| 31 |
"background": "https://ramaorientalfansub.tv/wp-content/uploads/2023/10/2860055-e1696595653601.jpg"
|
| 32 |
};
|
| 33 |
|
| 34 |
const builder = new addonBuilder(manifest);
|
|
|
|
| 35 |
|
| 36 |
-
// **GESTORE STREAM**
|
| 37 |
builder.defineStreamHandler(async ({ type, id }) => {
|
| 38 |
if (type !== "series") {
|
| 39 |
return Promise.resolve({ streams: [] });
|
| 40 |
}
|
| 41 |
|
| 42 |
console.log(`Richiesta stream per ID: ${id}`);
|
|
|
|
| 43 |
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
-
if (!episodes || episodes.length === 0) {
|
| 47 |
console.warn(`Nessun episodio trovato per ${id}`);
|
| 48 |
return Promise.resolve({ streams: [] });
|
| 49 |
}
|
| 50 |
|
| 51 |
-
const streams = episodes.flatMap(ep =>
|
| 52 |
ep.streams.map(stream => ({
|
| 53 |
title: `${ep.title} - ${stream.title}`,
|
| 54 |
url: stream.url,
|
| 55 |
type: "video/mp4",
|
| 56 |
behaviorHints: {
|
| 57 |
-
bingeGroup: id,
|
| 58 |
-
notWebReady: false
|
| 59 |
}
|
| 60 |
}))
|
| 61 |
);
|
|
@@ -63,8 +75,6 @@ builder.defineStreamHandler(async ({ type, id }) => {
|
|
| 63 |
return Promise.resolve({ streams });
|
| 64 |
});
|
| 65 |
|
| 66 |
-
|
| 67 |
-
// **GESTORE CATALOGHI**
|
| 68 |
builder.defineCatalogHandler(async (args) => {
|
| 69 |
if (args.type === 'series' && args.id === 'rama_series') {
|
| 70 |
return seriesCatalog(args);
|
|
@@ -73,11 +83,27 @@ builder.defineCatalogHandler(async (args) => {
|
|
| 73 |
}
|
| 74 |
});
|
| 75 |
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
export default builder.getInterface();
|
| 80 |
|
| 81 |
-
// **AVVIA IL SERVER**
|
| 82 |
serveHTTP(builder.getInterface(), { port: 7000 });
|
| 83 |
console.log(`Addon server is running at http://localhost:7000/manifest.json`);
|
|
|
|
| 2 |
import seriesCatalog from './rama_series.js';
|
| 3 |
import filmsCatalog from './rama_films.js';
|
| 4 |
import { getMeta } from './episodes.js';
|
|
|
|
| 5 |
|
| 6 |
const { addonBuilder, serveHTTP } = pkg;
|
| 7 |
|
|
|
|
| 15 |
"type": "series",
|
| 16 |
"id": "rama_series",
|
| 17 |
"name": "Serie Coreane",
|
| 18 |
+
"extra": [{ "name": "skip" }]
|
| 19 |
},
|
| 20 |
{
|
| 21 |
"type": "movie",
|
| 22 |
"id": "rama_films",
|
| 23 |
"name": "Film Coreani",
|
| 24 |
+
"extra": [{ "name": "skip" }]
|
| 25 |
}
|
| 26 |
],
|
| 27 |
+
"resources": ["catalog", "meta", "stream"],
|
| 28 |
"types": ["series", "movie"],
|
| 29 |
"logo": "https://ramaorientalfansub.tv/wp-content/uploads/2023/10/cropped-Logo-1.png",
|
| 30 |
"background": "https://ramaorientalfansub.tv/wp-content/uploads/2023/10/2860055-e1696595653601.jpg"
|
| 31 |
};
|
| 32 |
|
| 33 |
const builder = new addonBuilder(manifest);
|
| 34 |
+
const metaCache = new Map();
|
| 35 |
|
|
|
|
| 36 |
builder.defineStreamHandler(async ({ type, id }) => {
|
| 37 |
if (type !== "series") {
|
| 38 |
return Promise.resolve({ streams: [] });
|
| 39 |
}
|
| 40 |
|
| 41 |
console.log(`Richiesta stream per ID: ${id}`);
|
| 42 |
+
let meta = null;
|
| 43 |
|
| 44 |
+
if (metaCache.has(id)) {
|
| 45 |
+
console.log(`Caricamento metadati dalla cache per ${id}`);
|
| 46 |
+
meta = metaCache.get(id);
|
| 47 |
+
} else {
|
| 48 |
+
try {
|
| 49 |
+
const metaResult = await getMeta(id);
|
| 50 |
+
meta = metaResult.meta;
|
| 51 |
+
metaCache.set(id, meta);
|
| 52 |
+
} catch (error) {
|
| 53 |
+
console.error(`Errore nel caricamento dei metadati per ${id}:`, error);
|
| 54 |
+
return Promise.resolve({ streams: [] });
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
|
| 58 |
+
if (!meta || !meta.episodes || meta.episodes.length === 0) {
|
| 59 |
console.warn(`Nessun episodio trovato per ${id}`);
|
| 60 |
return Promise.resolve({ streams: [] });
|
| 61 |
}
|
| 62 |
|
| 63 |
+
const streams = meta.episodes.flatMap(ep =>
|
| 64 |
ep.streams.map(stream => ({
|
| 65 |
title: `${ep.title} - ${stream.title}`,
|
| 66 |
url: stream.url,
|
| 67 |
type: "video/mp4",
|
| 68 |
behaviorHints: {
|
| 69 |
+
bingeGroup: id,
|
| 70 |
+
notWebReady: false
|
| 71 |
}
|
| 72 |
}))
|
| 73 |
);
|
|
|
|
| 75 |
return Promise.resolve({ streams });
|
| 76 |
});
|
| 77 |
|
|
|
|
|
|
|
| 78 |
builder.defineCatalogHandler(async (args) => {
|
| 79 |
if (args.type === 'series' && args.id === 'rama_series') {
|
| 80 |
return seriesCatalog(args);
|
|
|
|
| 83 |
}
|
| 84 |
});
|
| 85 |
|
| 86 |
+
builder.defineMetaHandler(async (args) => {
|
| 87 |
+
let meta = null;
|
| 88 |
+
|
| 89 |
+
if (metaCache.has(args.id)) {
|
| 90 |
+
console.log(`Caricamento metadati dalla cache per ${args.id}`);
|
| 91 |
+
meta = { meta: metaCache.get(args.id) };
|
| 92 |
+
} else {
|
| 93 |
+
try {
|
| 94 |
+
const metaResult = await getMeta(args.id);
|
| 95 |
+
meta = metaResult.meta;
|
| 96 |
+
metaCache.set(args.id, meta);
|
| 97 |
+
} catch (error) {
|
| 98 |
+
console.error(`Errore nel caricamento dei metadati per ${args.id}:`, error);
|
| 99 |
+
return { meta: null };
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
return { meta: { ...meta, extra: meta.extra } };
|
| 104 |
+
});
|
| 105 |
|
| 106 |
export default builder.getInterface();
|
| 107 |
|
|
|
|
| 108 |
serveHTTP(builder.getInterface(), { port: 7000 });
|
| 109 |
console.log(`Addon server is running at http://localhost:7000/manifest.json`);
|
package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
CHANGED
|
@@ -1,22 +1,23 @@
|
|
| 1 |
-
{
|
| 2 |
-
"name": "stremio-rama-addon",
|
| 3 |
-
"version": "1.0.
|
| 4 |
-
"
|
| 5 |
-
"
|
| 6 |
-
"
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
"
|
| 14 |
-
"
|
| 15 |
-
"
|
| 16 |
-
"puppeteer
|
| 17 |
-
"puppeteer-
|
| 18 |
-
"puppeteer-extra
|
| 19 |
-
"puppeteer-extra-plugin-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
}
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "stremio-rama-addon",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"type": "module",
|
| 5 |
+
"description": "Addon per Stremio per visualizzare serie e film coreani dal sito Rama Oriental Fansub",
|
| 6 |
+
"main": "main.js",
|
| 7 |
+
"scripts": {
|
| 8 |
+
"start": "node main.js"
|
| 9 |
+
},
|
| 10 |
+
"author": "Il tuo nome",
|
| 11 |
+
"license": "ISC",
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"axios": "^1.5.0",
|
| 14 |
+
"cheerio": "^1.0.0-rc.12",
|
| 15 |
+
"cloudscraper": "4.6.0",
|
| 16 |
+
"puppeteer": "24.3.1",
|
| 17 |
+
"puppeteer-core": "^24.3.1",
|
| 18 |
+
"puppeteer-extra": "^3.3.6",
|
| 19 |
+
"puppeteer-extra-plugin-recaptcha": "^3.6.8",
|
| 20 |
+
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
| 21 |
+
"stremio-addon-sdk": "^1.6.10"
|
| 22 |
+
}
|
| 23 |
+
}
|
rama_series.js
CHANGED
|
@@ -1,34 +1,122 @@
|
|
| 1 |
-
import
|
| 2 |
import * as cheerio from 'cheerio';
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
const
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
})
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
-
export default
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cloudscraper from 'cloudscraper';
|
| 2 |
import * as cheerio from 'cheerio';
|
| 3 |
|
| 4 |
+
const userAgents = [
|
| 5 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.3',
|
| 6 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0',
|
| 7 |
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Safari/605.1.15'
|
| 8 |
+
];
|
| 9 |
+
|
| 10 |
+
function getRandomHeaders() {
|
| 11 |
+
const userAgent = userAgents[Math.floor(Math.random() * userAgents.length)];
|
| 12 |
+
return {
|
| 13 |
+
'User-Agent': userAgent,
|
| 14 |
+
'Referer': 'https://ramaorientalfansub.tv/paese/corea-del-sud/',
|
| 15 |
+
'Accept-Language': 'en-US,en;q=0.9'
|
| 16 |
+
};
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
async function fetchWithCloudscraper(url) {
|
| 20 |
+
try {
|
| 21 |
+
const data = await cloudscraper.get({
|
| 22 |
+
uri: url,
|
| 23 |
+
headers: getRandomHeaders()
|
| 24 |
+
});
|
| 25 |
+
return data;
|
| 26 |
+
} catch (error) {
|
| 27 |
+
console.error("Errore con Cloudscraper:", error);
|
| 28 |
+
return null;
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
const BASE_URL = 'https://ramaorientalfansub.tv/paese/corea-del-sud/';
|
| 33 |
+
const ITEMS_PER_PAGE = 25;
|
| 34 |
+
const MAX_PAGES = 35;
|
| 35 |
+
const catalogCache = new Map();
|
| 36 |
+
|
| 37 |
+
async function getCatalog(skip = 0) {
|
| 38 |
+
const catalog = [];
|
| 39 |
+
let pageNumber = Math.floor(skip / ITEMS_PER_PAGE) + 1;
|
| 40 |
+
let itemsToLoad = ITEMS_PER_PAGE;
|
| 41 |
+
|
| 42 |
+
while (catalog.length < itemsToLoad && pageNumber <= MAX_PAGES) {
|
| 43 |
+
const pageUrl = `${BASE_URL}page/${pageNumber}/`;
|
| 44 |
+
console.log(`Richiedendo pagina: ${pageNumber}`);
|
| 45 |
+
|
| 46 |
+
let data;
|
| 47 |
+
if (catalogCache.has(pageUrl)) {
|
| 48 |
+
console.log(`Caricamento pagina ${pageNumber} dalla cache.`);
|
| 49 |
+
data = catalogCache.get(pageUrl);
|
| 50 |
+
} else {
|
| 51 |
+
try {
|
| 52 |
+
data = await fetchWithCloudscraper(pageUrl);
|
| 53 |
+
if (!data) {
|
| 54 |
+
console.warn(`Nessun dato ricevuto per ${pageUrl}`);
|
| 55 |
+
pageNumber++;
|
| 56 |
+
continue;
|
| 57 |
+
}
|
| 58 |
+
catalogCache.set(pageUrl, data);
|
| 59 |
+
} catch (error) {
|
| 60 |
+
console.error(`Errore nel caricamento della pagina ${pageNumber}:`, error);
|
| 61 |
+
pageNumber++;
|
| 62 |
+
continue;
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
const $ = cheerio.load(data);
|
| 67 |
+
let foundItemsOnPage = 0;
|
| 68 |
+
|
| 69 |
+
$('div.w-full.bg-gradient-to-t.from-primary').each((index, element) => {
|
| 70 |
+
if (catalog.length >= itemsToLoad) {
|
| 71 |
+
return false; // Interrompe il ciclo .each()
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
const titleElement = $(element).find('a.text-sm.line-clamp-2.font-medium.leading-snug.lg\\:leading-normal');
|
| 75 |
+
const title = titleElement.text().trim();
|
| 76 |
+
const link = titleElement.attr('href');
|
| 77 |
+
const poster = $(element).find('img').attr('src');
|
| 78 |
+
const tagElement = $(element).find('.inline-block.md\\:my-3.uppercase');
|
| 79 |
+
const tagText = tagElement.text().trim().toLowerCase();
|
| 80 |
+
let extraTag = null;
|
| 81 |
+
|
| 82 |
+
if (tagText.includes('completed')) {
|
| 83 |
+
extraTag = 'completed';
|
| 84 |
+
} else if (tagText.includes('simulcast')) {
|
| 85 |
+
extraTag = 'simulcast';
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
if (tagText.includes('tv')) {
|
| 89 |
+
if (title && link) {
|
| 90 |
+
const formattedTitle = title.replace(/\s+/g, '-').toLowerCase().replace(/[()]/g, '');
|
| 91 |
+
const meta = {
|
| 92 |
+
id: formattedTitle,
|
| 93 |
+
type: 'series',
|
| 94 |
+
name: title,
|
| 95 |
+
poster,
|
| 96 |
+
description: title,
|
| 97 |
+
imdbRating: "N/A",
|
| 98 |
+
released: 2024,
|
| 99 |
+
};
|
| 100 |
+
|
| 101 |
+
if (extraTag) {
|
| 102 |
+
meta.extra = { tag: extraTag };
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
catalog.push(meta);
|
| 106 |
+
foundItemsOnPage++;
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
});
|
| 110 |
+
|
| 111 |
+
console.log(`Pagina ${pageNumber}: trovati ${foundItemsOnPage} elementi.`);
|
| 112 |
+
pageNumber++;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
console.log(`Totale elementi raccolti: ${catalog.length}`);
|
| 116 |
+
return catalog;
|
| 117 |
}
|
| 118 |
|
| 119 |
+
export default async function (args) {
|
| 120 |
+
const skip = args.extra?.skip || 0;
|
| 121 |
+
return { metas: await getCatalog(skip) };
|
| 122 |
+
};
|
streams.js
CHANGED
|
@@ -1,32 +1,35 @@
|
|
| 1 |
-
import axios from 'axios';
|
| 2 |
import cloudscraper from 'cloudscraper';
|
| 3 |
import * as cheerio from 'cheerio';
|
| 4 |
-
import puppeteer from 'puppeteer';
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
return null;
|
| 27 |
}
|
| 28 |
-
|
| 29 |
-
return cheerio.load(data);
|
| 30 |
}
|
| 31 |
|
| 32 |
/**
|
|
@@ -35,17 +38,27 @@ async function fetchSeriesData(seriesLink) {
|
|
| 35 |
* @returns {string|null} - URL dello stream o null se non trovato.
|
| 36 |
*/
|
| 37 |
async function getStream(episodeLink) {
|
|
|
|
| 38 |
try {
|
| 39 |
-
const
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
} else {
|
| 48 |
-
console.warn(`
|
| 49 |
return null;
|
| 50 |
}
|
| 51 |
} catch (err) {
|
|
@@ -59,27 +72,21 @@ async function getStream(episodeLink) {
|
|
| 59 |
/**
|
| 60 |
* Recupera la lista degli episodi e i rispettivi stream per una serie.
|
| 61 |
* @param {string} seriesLink - URL della pagina della serie.
|
|
|
|
| 62 |
* @returns {Array} - Lista di episodi con titolo, thumbnail e link allo stream.
|
| 63 |
*/
|
| 64 |
-
async function getEpisodes(seriesLink) {
|
| 65 |
try {
|
| 66 |
const episodes = [];
|
| 67 |
const baseEpisodeUrl = seriesLink.replace('/drama/', '/watch/');
|
| 68 |
-
|
| 69 |
console.log('Chiamata a getEpisodes con URL:', seriesLink);
|
| 70 |
-
|
| 71 |
let seriesId = seriesLink.split('/').filter(Boolean).pop();
|
| 72 |
console.log('ID della serie estratto prima della pulizia:', seriesId);
|
| 73 |
-
|
| 74 |
seriesId = seriesId.replace(/,/g, '').replace(/\s+/g, '-').toLowerCase();
|
| 75 |
console.log('ID della serie dopo la pulizia:', seriesId);
|
| 76 |
-
|
| 77 |
let seriesYear = null;
|
| 78 |
-
|
| 79 |
try {
|
| 80 |
-
const { data } = await axiosInstance.get(seriesLink);
|
| 81 |
-
const $ = cheerio.load(data);
|
| 82 |
-
|
| 83 |
const titleText = $('title').text();
|
| 84 |
const yearMatch = titleText.match(/\b(19|20)\d{2}\b/);
|
| 85 |
if (yearMatch) {
|
|
@@ -90,34 +97,40 @@ async function getEpisodes(seriesLink) {
|
|
| 90 |
}
|
| 91 |
|
| 92 |
console.log('Anno serie:', seriesYear);
|
| 93 |
-
|
| 94 |
-
|
|
|
|
| 95 |
const episodeId = seriesYear ? `${seriesId}-${seriesYear}` : seriesId;
|
| 96 |
const episodeLink = `https://ramaorientalfansub.tv/watch/${episodeId}-episodio-${episodeNumber}/`;
|
| 97 |
console.log(`URL dell'episodio generato: ${episodeLink}`);
|
| 98 |
-
|
| 99 |
try {
|
| 100 |
const stream = await getStream(episodeLink);
|
| 101 |
-
if (!stream)
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
episodes.push({
|
| 104 |
id: `episodio-${episodeNumber}`,
|
| 105 |
-
title: `
|
| 106 |
-
thumbnail: '
|
| 107 |
-
streams: [
|
| 108 |
-
{
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
}
|
| 113 |
-
]
|
| 114 |
});
|
| 115 |
-
|
| 116 |
-
|
|
|
|
| 117 |
console.error(`Errore durante il recupero dello stream per ${episodeLink}:`, err);
|
|
|
|
| 118 |
}
|
|
|
|
|
|
|
| 119 |
}
|
| 120 |
|
|
|
|
| 121 |
return episodes;
|
| 122 |
} catch (err) {
|
| 123 |
console.error('Errore durante il recupero degli episodi:', err);
|
|
@@ -125,9 +138,4 @@ async function getEpisodes(seriesLink) {
|
|
| 125 |
}
|
| 126 |
}
|
| 127 |
|
| 128 |
-
export { getEpisodes };
|
| 129 |
-
|
| 130 |
-
(async () => {
|
| 131 |
-
const seriesLink = "https://ramaorientalfansub.tv/drama/";
|
| 132 |
-
const $ = await fetchSeriesData(seriesLink);
|
| 133 |
-
})();
|
|
|
|
|
|
|
| 1 |
import cloudscraper from 'cloudscraper';
|
| 2 |
import * as cheerio from 'cheerio';
|
|
|
|
| 3 |
|
| 4 |
+
// Lista di User-Agent
|
| 5 |
+
const userAgents = [
|
| 6 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.3',
|
| 7 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0',
|
| 8 |
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Safari/605.1.15'
|
| 9 |
+
];
|
| 10 |
+
|
| 11 |
+
// Funzione per ottenere headers casuali
|
| 12 |
+
function getRandomHeaders() {
|
| 13 |
+
const userAgent = userAgents[Math.floor(Math.random() * userAgents.length)];
|
| 14 |
+
return {
|
| 15 |
+
'User-Agent': userAgent,
|
| 16 |
+
'Referer': 'https://ramaorientalfansub.tv', // Assicurati che il referer sia corretto
|
| 17 |
+
'Accept-Language': 'en-US,en;q=0.9'
|
| 18 |
+
};
|
| 19 |
+
}
|
| 20 |
|
| 21 |
+
async function fetchWithCloudscraper(url) {
|
| 22 |
+
try {
|
| 23 |
+
const data = await cloudscraper.get({
|
| 24 |
+
uri: url,
|
| 25 |
+
headers: getRandomHeaders() // Aggiungi headers casuali
|
| 26 |
+
});
|
| 27 |
+
return data;
|
| 28 |
+
} catch (error) {
|
| 29 |
+
console.error("Errore con Cloudscraper:", error);
|
| 30 |
+
// Gestisci l'errore e restituisci null o un valore appropriato
|
| 31 |
return null;
|
| 32 |
}
|
|
|
|
|
|
|
| 33 |
}
|
| 34 |
|
| 35 |
/**
|
|
|
|
| 38 |
* @returns {string|null} - URL dello stream o null se non trovato.
|
| 39 |
*/
|
| 40 |
async function getStream(episodeLink) {
|
| 41 |
+
let iframeSrc = null;
|
| 42 |
try {
|
| 43 |
+
const data = await fetchWithCloudscraper(episodeLink);
|
| 44 |
+
if (!data) {
|
| 45 |
+
console.warn(`Nessun dato ricevuto per ${episodeLink}`);
|
| 46 |
+
return null;
|
| 47 |
+
}
|
| 48 |
|
| 49 |
+
const $ = cheerio.load(data);
|
| 50 |
+
iframeSrc = $('iframe').attr('src');
|
| 51 |
+
if (iframeSrc) {
|
| 52 |
+
iframeSrc = encodeURI(iframeSrc);
|
| 53 |
+
console.log(`Stream trovato: ${decodeURI(iframeSrc)}`);
|
| 54 |
+
if (iframeSrc.startsWith('https://server1.streamingrof.online/02-DRAMACOREANI')) {
|
| 55 |
+
return iframeSrc;
|
| 56 |
+
} else {
|
| 57 |
+
console.warn(`Stream non valido per ${episodeLink}: ${iframeSrc}`);
|
| 58 |
+
return null;
|
| 59 |
+
}
|
| 60 |
} else {
|
| 61 |
+
console.warn(`Nessun iframe trovato per ${episodeLink}`);
|
| 62 |
return null;
|
| 63 |
}
|
| 64 |
} catch (err) {
|
|
|
|
| 72 |
/**
|
| 73 |
* Recupera la lista degli episodi e i rispettivi stream per una serie.
|
| 74 |
* @param {string} seriesLink - URL della pagina della serie.
|
| 75 |
+
* @param {CheerioStatic} $ - Oggetto Cheerio caricato con la pagina della serie.
|
| 76 |
* @returns {Array} - Lista di episodi con titolo, thumbnail e link allo stream.
|
| 77 |
*/
|
| 78 |
+
async function getEpisodes(seriesLink, $) {
|
| 79 |
try {
|
| 80 |
const episodes = [];
|
| 81 |
const baseEpisodeUrl = seriesLink.replace('/drama/', '/watch/');
|
|
|
|
| 82 |
console.log('Chiamata a getEpisodes con URL:', seriesLink);
|
|
|
|
| 83 |
let seriesId = seriesLink.split('/').filter(Boolean).pop();
|
| 84 |
console.log('ID della serie estratto prima della pulizia:', seriesId);
|
|
|
|
| 85 |
seriesId = seriesId.replace(/,/g, '').replace(/\s+/g, '-').toLowerCase();
|
| 86 |
console.log('ID della serie dopo la pulizia:', seriesId);
|
|
|
|
| 87 |
let seriesYear = null;
|
| 88 |
+
// Estrai l'anno dalla pagina della serie (se presente)
|
| 89 |
try {
|
|
|
|
|
|
|
|
|
|
| 90 |
const titleText = $('title').text();
|
| 91 |
const yearMatch = titleText.match(/\b(19|20)\d{2}\b/);
|
| 92 |
if (yearMatch) {
|
|
|
|
| 97 |
}
|
| 98 |
|
| 99 |
console.log('Anno serie:', seriesYear);
|
| 100 |
+
// Invece di cercare il numero massimo di episodi, proviamo semplicemente a caricarli in sequenza
|
| 101 |
+
let episodeNumber = 1;
|
| 102 |
+
while (true) {
|
| 103 |
const episodeId = seriesYear ? `${seriesId}-${seriesYear}` : seriesId;
|
| 104 |
const episodeLink = `https://ramaorientalfansub.tv/watch/${episodeId}-episodio-${episodeNumber}/`;
|
| 105 |
console.log(`URL dell'episodio generato: ${episodeLink}`);
|
|
|
|
| 106 |
try {
|
| 107 |
const stream = await getStream(episodeLink);
|
| 108 |
+
if (!stream) {
|
| 109 |
+
console.log(`Stream non trovato per l'episodio ${episodeNumber}, interrompendo la ricerca.`);
|
| 110 |
+
break; // Interrompi il ciclo se lo stream non viene trovato
|
| 111 |
+
}
|
| 112 |
|
| 113 |
episodes.push({
|
| 114 |
id: `episodio-${episodeNumber}`,
|
| 115 |
+
title: `${episodeNumber}`,
|
| 116 |
+
thumbnail: 'div.swiper-slide:nth-child(1) > a:nth-child(1) > div:nth-child(1)',
|
| 117 |
+
streams: [{
|
| 118 |
+
title: `Episodio ${episodeNumber}`,
|
| 119 |
+
url: stream,
|
| 120 |
+
type: "video/mp4"
|
| 121 |
+
}]
|
|
|
|
|
|
|
| 122 |
});
|
| 123 |
+
// Aggiungi un ritardo tra le richieste per ogni episodio
|
| 124 |
+
await new Promise(resolve => setTimeout(resolve, 1000)); // Aspetta 1 secondo
|
| 125 |
+
} catch (error) {
|
| 126 |
console.error(`Errore durante il recupero dello stream per ${episodeLink}:`, err);
|
| 127 |
+
break; // Interrompi il ciclo anche in caso di errore
|
| 128 |
}
|
| 129 |
+
|
| 130 |
+
episodeNumber++;
|
| 131 |
}
|
| 132 |
|
| 133 |
+
console.log('Episodi trovati:', JSON.stringify(episodes, null, 2));
|
| 134 |
return episodes;
|
| 135 |
} catch (err) {
|
| 136 |
console.error('Errore durante il recupero degli episodi:', err);
|
|
|
|
| 138 |
}
|
| 139 |
}
|
| 140 |
|
| 141 |
+
export { getEpisodes, getStream }; // Esporta anche getStream
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|