Spaces:
Paused
Paused
Upload 9 files
Browse files- Dockerfile +27 -27
- episodes.js +57 -27
- main.js +26 -12
- manifest.json +19 -19
- package-lock.json +34 -19
- rama_series.js +120 -112
Dockerfile
CHANGED
|
@@ -1,27 +1,27 @@
|
|
| 1 |
-
# Usa una immagine di Node.js come base
|
| 2 |
-
FROM node:20-alpine
|
| 3 |
-
|
| 4 |
-
# Imposta la directory di lavoro all'interno del container
|
| 5 |
-
WORKDIR /app
|
| 6 |
-
|
| 7 |
-
RUN npm install -g npm@11.2.0
|
| 8 |
-
|
| 9 |
-
# Copia i file package.json e package-lock.json (se presente)
|
| 10 |
-
COPY package.json ./
|
| 11 |
-
|
| 12 |
-
RUN npm cache clean --force
|
| 13 |
-
|
| 14 |
-
# Installa le dipendenze del progetto
|
| 15 |
-
RUN npm install
|
| 16 |
-
|
| 17 |
-
# Copia il resto dei file del progetto nella directory di lavoro
|
| 18 |
-
COPY . .
|
| 19 |
-
|
| 20 |
-
# Definisci la variabile d'ambiente (opzionale)
|
| 21 |
-
# ENV PROXY_URL=""
|
| 22 |
-
|
| 23 |
-
# Esponi la porta su cui l'addon sarà in ascolto
|
| 24 |
-
EXPOSE 7000
|
| 25 |
-
|
| 26 |
-
# Comando per avviare l'applicazione
|
| 27 |
-
CMD [ "node", "main.js" ]
|
|
|
|
| 1 |
+
# Usa una immagine di Node.js come base
|
| 2 |
+
FROM node:20-alpine
|
| 3 |
+
|
| 4 |
+
# Imposta la directory di lavoro all'interno del container
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
RUN npm install -g npm@11.2.0
|
| 8 |
+
|
| 9 |
+
# Copia i file package.json e package-lock.json (se presente)
|
| 10 |
+
COPY package.json ./
|
| 11 |
+
|
| 12 |
+
RUN npm cache clean --force
|
| 13 |
+
|
| 14 |
+
# Installa le dipendenze del progetto
|
| 15 |
+
RUN npm install
|
| 16 |
+
|
| 17 |
+
# Copia il resto dei file del progetto nella directory di lavoro
|
| 18 |
+
COPY . .
|
| 19 |
+
|
| 20 |
+
# Definisci la variabile d'ambiente (opzionale)
|
| 21 |
+
# ENV PROXY_URL=""
|
| 22 |
+
|
| 23 |
+
# Esponi la porta su cui l'addon sarà in ascolto
|
| 24 |
+
EXPOSE 7000
|
| 25 |
+
|
| 26 |
+
# Comando per avviare l'applicazione
|
| 27 |
+
CMD [ "node", "main.js" ]
|
episodes.js
CHANGED
|
@@ -3,16 +3,6 @@ import cloudscraper from 'cloudscraper';
|
|
| 3 |
import * as cheerio from 'cheerio';
|
| 4 |
import { getStream } from './streams.js';
|
| 5 |
|
| 6 |
-
const DEBUG_MODE = process.env.DEBUG === 'true';
|
| 7 |
-
const CLOUDFLARE_RETRIES = process.env.CLOUDFLARE_RETRIES || 2;
|
| 8 |
-
const META_CACHE_TTL = 30000; // 30 sec
|
| 9 |
-
|
| 10 |
-
const userAgents = [
|
| 11 |
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36',
|
| 12 |
-
'Mozilla/5.0 (Windows NT 10.0; rv:125.0) Gecko/20100101 Firefox/125.0',
|
| 13 |
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15',
|
| 14 |
-
'Mozilla/5.0 (Linux; Android 14; SM-S928U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.140 Mobile Safari/537.36'
|
| 15 |
-
];
|
| 16 |
|
| 17 |
const metaCache = new Map();
|
| 18 |
|
|
@@ -45,46 +35,51 @@ async function fetchWithCloudscraper(url, retries = 2) {
|
|
| 45 |
headers: getRandomHeaders(),
|
| 46 |
followAllRedirects: true,
|
| 47 |
maxRedirects: 2,
|
| 48 |
-
timeout:
|
| 49 |
resolveWithFullResponse: true // Aggiungi questa opzione
|
| 50 |
});
|
| 51 |
|
|
|
|
| 52 |
if (response.statusCode === 404) {
|
| 53 |
-
console.warn(`
|
| 54 |
-
return null;
|
| 55 |
}
|
| 56 |
|
| 57 |
-
if (response
|
| 58 |
-
console.log(`[${i + 1}/${retries}]
|
| 59 |
-
return response.body;
|
| 60 |
-
} else {
|
| 61 |
-
console.warn(`[${i + 1}/${retries}] Errore ${response.statusCode} per ${url}. Riprovo...`);
|
| 62 |
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
| 63 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
} catch (error) {
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
if (error.message.includes('Cloudflare')) {
|
| 67 |
-
console.warn(`[${i + 1}/${retries}] Rilevato Cloudflare. Attendo 10 secondi...`);
|
| 68 |
await new Promise(resolve => setTimeout(resolve, 10000));
|
| 69 |
} else {
|
| 70 |
-
console.warn(`[${i + 1}/${retries}] Errore generico. Attendo 2 secondi...`);
|
| 71 |
await new Promise(resolve => setTimeout(resolve, 2000));
|
| 72 |
}
|
| 73 |
}
|
| 74 |
}
|
| 75 |
-
console.error(`
|
| 76 |
return null;
|
| 77 |
}
|
| 78 |
|
| 79 |
-
|
| 80 |
async function getMeta(id) {
|
| 81 |
-
const meta = { id, type: 'series', name: '', poster: '', episodes:
|
| 82 |
const cleanId = id.replace(/,/g, '-').toLowerCase();
|
| 83 |
const baseId = cleanId.replace(/-\d{4}$/, '');
|
| 84 |
const seriesLink = `https://ramaorientalfansub.tv/drama/${baseId}/`;
|
| 85 |
|
| 86 |
if (metaCache.has(id)) {
|
| 87 |
-
|
|
|
|
| 88 |
}
|
| 89 |
|
| 90 |
try {
|
|
@@ -97,11 +92,25 @@ async function getMeta(id) {
|
|
| 97 |
const $ = cheerio.load(data);
|
| 98 |
meta.name = $('a.text-accent').text().trim();
|
| 99 |
meta.poster = $('img.wp-post-image').attr('src');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
let description = $('div.font-light > div:nth-child(1)').text().trim();
|
| 101 |
if (meta.extra && meta.extra.tag) {
|
| 102 |
description += ` [${meta.extra.tag.toUpperCase()}]`;
|
| 103 |
}
|
| 104 |
meta.description = description;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
// Recupera gli episodi
|
| 107 |
meta.episodes = await getEpisodes(seriesLink, $, baseId); // Passa baseId a getEpisodes
|
|
@@ -151,10 +160,31 @@ async function getEpisodes(seriesLink, $, baseId) { // baseId come parametro
|
|
| 151 |
break; // Interrompi il ciclo while
|
| 152 |
}
|
| 153 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
episodes.push({
|
| 155 |
id: `episodio-${episodeNumber}`,
|
| 156 |
title: `Episodio ${episodeNumber}`,
|
| 157 |
-
thumbnail: '
|
| 158 |
streams: [{
|
| 159 |
title: `Episodio ${episodeNumber}`,
|
| 160 |
url: stream,
|
|
|
|
| 3 |
import * as cheerio from 'cheerio';
|
| 4 |
import { getStream } from './streams.js';
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
const metaCache = new Map();
|
| 8 |
|
|
|
|
| 35 |
headers: getRandomHeaders(),
|
| 36 |
followAllRedirects: true,
|
| 37 |
maxRedirects: 2,
|
| 38 |
+
timeout: 10000,
|
| 39 |
resolveWithFullResponse: true // Aggiungi questa opzione
|
| 40 |
});
|
| 41 |
|
| 42 |
+
// Gestione 404 semplificata
|
| 43 |
if (response.statusCode === 404) {
|
| 44 |
+
console.warn(`⚠️ [404] Pagina non trovata: ${url}`);
|
| 45 |
+
return null;
|
| 46 |
}
|
| 47 |
|
| 48 |
+
if (response.statusCode >= 200 && response.statusCode < 300) {
|
| 49 |
+
console.log(`✅ [${i + 1}/${retries}] Successo: ${url}`);
|
| 50 |
+
return response.body;
|
|
|
|
|
|
|
|
|
|
| 51 |
}
|
| 52 |
+
|
| 53 |
+
console.warn(`⚠️ [${i + 1}/${retries}] Errore HTTP ${response.statusCode} per ${url}`);
|
| 54 |
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
| 55 |
+
|
| 56 |
} catch (error) {
|
| 57 |
+
// Gestione errori senza mostrare l'HTML
|
| 58 |
+
const errorMessage = error.response
|
| 59 |
+
? `Errore ${error.response.statusCode}: ${error.message}`
|
| 60 |
+
: error.message;
|
| 61 |
+
|
| 62 |
+
console.warn(`⚠️ [${i + 1}/${retries}] ${errorMessage}`);
|
| 63 |
if (error.message.includes('Cloudflare')) {
|
|
|
|
| 64 |
await new Promise(resolve => setTimeout(resolve, 10000));
|
| 65 |
} else {
|
|
|
|
| 66 |
await new Promise(resolve => setTimeout(resolve, 2000));
|
| 67 |
}
|
| 68 |
}
|
| 69 |
}
|
| 70 |
+
console.error(`❌ Impossibile recuperare ${url}`);
|
| 71 |
return null;
|
| 72 |
}
|
| 73 |
|
|
|
|
| 74 |
async function getMeta(id) {
|
| 75 |
+
const meta = { id, type: 'series', name: '', poster: '', episodes: null };
|
| 76 |
const cleanId = id.replace(/,/g, '-').toLowerCase();
|
| 77 |
const baseId = cleanId.replace(/-\d{4}$/, '');
|
| 78 |
const seriesLink = `https://ramaorientalfansub.tv/drama/${baseId}/`;
|
| 79 |
|
| 80 |
if (metaCache.has(id)) {
|
| 81 |
+
const cachedMeta = metaCache.get(id);
|
| 82 |
+
return { meta: { ...cachedMeta } }; // Restituisci una copia per evitare modifiche dirette
|
| 83 |
}
|
| 84 |
|
| 85 |
try {
|
|
|
|
| 92 |
const $ = cheerio.load(data);
|
| 93 |
meta.name = $('a.text-accent').text().trim();
|
| 94 |
meta.poster = $('img.wp-post-image').attr('src');
|
| 95 |
+
// **NUOVA LOGICA PER RECUPERARE LA THUMBNAIL**
|
| 96 |
+
let thumbnail = $('div.thumbnail_url_episode_list > img').attr('data-src'); // Prova a prendere l'immagine con il nuovo selettore
|
| 97 |
+
if (!thumbnail) {
|
| 98 |
+
thumbnail = $('img.wp-post-image').attr('src'); // Se non la trova, usa il metodo precedente
|
| 99 |
+
console.log('Usando thumbnail wp-post-image'); // Log per debug
|
| 100 |
+
} else {
|
| 101 |
+
console.log('Usando thumbnail thumbnail_url_episode_list'); // Log per debug
|
| 102 |
+
}
|
| 103 |
+
meta.poster = thumbnail; // Assegna la thumbnail (trovata con uno dei due metodi) al poster
|
| 104 |
+
|
| 105 |
let description = $('div.font-light > div:nth-child(1)').text().trim();
|
| 106 |
if (meta.extra && meta.extra.tag) {
|
| 107 |
description += ` [${meta.extra.tag.toUpperCase()}]`;
|
| 108 |
}
|
| 109 |
meta.description = description;
|
| 110 |
+
meta.seriesLink = seriesLink;
|
| 111 |
+
meta.baseId = baseId;
|
| 112 |
+
|
| 113 |
+
metaCache.set(id, meta);
|
| 114 |
|
| 115 |
// Recupera gli episodi
|
| 116 |
meta.episodes = await getEpisodes(seriesLink, $, baseId); // Passa baseId a getEpisodes
|
|
|
|
| 160 |
break; // Interrompi il ciclo while
|
| 161 |
}
|
| 162 |
|
| 163 |
+
const episodeData = await fetchWithCloudscraper(episodeLink);
|
| 164 |
+
if (!episodeData) {
|
| 165 |
+
console.warn(`Nessun dato ricevuto per ${episodeLink} durante il recupero della miniatura.`);
|
| 166 |
+
break;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
const $$ = cheerio.load(episodeData); // Usa un'istanza separata di Cheerio
|
| 170 |
+
|
| 171 |
+
// **Selettore per la miniatura**
|
| 172 |
+
const thumbnailElement = $$('div.thumbnail_url_episode_list img.lazyloaded');
|
| 173 |
+
let thumbnailUrl = thumbnailElement.attr('data-src');
|
| 174 |
+
|
| 175 |
+
if (!thumbnailUrl) {
|
| 176 |
+
thumbnailUrl = thumbnailElement.attr('src'); //Fallback a src
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
if (!thumbnailUrl) {
|
| 180 |
+
console.warn(`Nessuna miniatura trovata per ${episodeLink}`);
|
| 181 |
+
thumbnailUrl = null; // Imposta a null se non trovata
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
episodes.push({
|
| 185 |
id: `episodio-${episodeNumber}`,
|
| 186 |
title: `Episodio ${episodeNumber}`,
|
| 187 |
+
thumbnail: 'thumbnailUrl',
|
| 188 |
streams: [{
|
| 189 |
title: `Episodio ${episodeNumber}`,
|
| 190 |
url: stream,
|
main.js
CHANGED
|
@@ -3,26 +3,26 @@ import seriesCatalog from './rama_series.js';
|
|
| 3 |
import { getMeta } from './episodes.js';
|
| 4 |
|
| 5 |
const { addonBuilder, serveHTTP } = pkg;
|
| 6 |
-
const META_CACHE_TTL =
|
| 7 |
|
| 8 |
const manifest = {
|
| 9 |
"id": "community.ramaorientalfansub",
|
| 10 |
"version": "1.0.6",
|
| 11 |
-
"name": "Rama Oriental Fansub+",
|
| 12 |
"description": "Addon migliorato con scraper avanzato",
|
| 13 |
"catalogs": [
|
| 14 |
{
|
| 15 |
"type": "series",
|
| 16 |
"id": "rama_series",
|
| 17 |
-
"name": "Serie Coreane
|
| 18 |
"extra": [{ "name": "skip" }]
|
| 19 |
}
|
| 20 |
|
| 21 |
],
|
| 22 |
"resources": ["catalog", "meta", "stream"],
|
| 23 |
"types": ["series"],
|
| 24 |
-
"logo": "https://
|
| 25 |
-
"background": "https://
|
| 26 |
};
|
| 27 |
|
| 28 |
const builder = new addonBuilder(manifest);
|
|
@@ -34,12 +34,20 @@ builder.defineStreamHandler(async ({ type, id }) => {
|
|
| 34 |
if (type !== "series") return { streams: [] };
|
| 35 |
|
| 36 |
let meta = metaCache.get(id);
|
| 37 |
-
if (!meta
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
|
|
|
| 43 |
return {
|
| 44 |
streams: meta.episodes?.flatMap(ep =>
|
| 45 |
ep.streams.map(stream => ({
|
|
@@ -48,8 +56,13 @@ builder.defineStreamHandler(async ({ type, id }) => {
|
|
| 48 |
type: "video/mp4",
|
| 49 |
behaviorHints: { bingeGroup: id }
|
| 50 |
}))
|
| 51 |
-
)
|
| 52 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
} catch (error) {
|
| 54 |
console.error(`Handler Error: ${error.message}`);
|
| 55 |
return { streams: [] };
|
|
@@ -60,7 +73,8 @@ builder.defineCatalogHandler(async (args) => {
|
|
| 60 |
console.log("Catalog Handler chiamato con:", args); // Aggiungi questo log
|
| 61 |
if (args.type === 'series' && args.id === 'rama_series') {
|
| 62 |
return seriesCatalog(args);
|
| 63 |
-
|
|
|
|
| 64 |
return { metas: [] }; // Aggiungi un return di default
|
| 65 |
});
|
| 66 |
|
|
|
|
| 3 |
import { getMeta } from './episodes.js';
|
| 4 |
|
| 5 |
const { addonBuilder, serveHTTP } = pkg;
|
| 6 |
+
const META_CACHE_TTL = 600000;
|
| 7 |
|
| 8 |
const manifest = {
|
| 9 |
"id": "community.ramaorientalfansub",
|
| 10 |
"version": "1.0.6",
|
| 11 |
+
"name": "Rama Oriental Fansub +",
|
| 12 |
"description": "Addon migliorato con scraper avanzato",
|
| 13 |
"catalogs": [
|
| 14 |
{
|
| 15 |
"type": "series",
|
| 16 |
"id": "rama_series",
|
| 17 |
+
"name": "Serie Coreane",
|
| 18 |
"extra": [{ "name": "skip" }]
|
| 19 |
}
|
| 20 |
|
| 21 |
],
|
| 22 |
"resources": ["catalog", "meta", "stream"],
|
| 23 |
"types": ["series"],
|
| 24 |
+
"logo": "https://i.imgur.com/i7VdVv7.png",
|
| 25 |
+
"background": "https://i.imgur.com/mtsxMk7.jpeg"
|
| 26 |
};
|
| 27 |
|
| 28 |
const builder = new addonBuilder(manifest);
|
|
|
|
| 34 |
if (type !== "series") return { streams: [] };
|
| 35 |
|
| 36 |
let meta = metaCache.get(id);
|
| 37 |
+
if (!meta) {
|
| 38 |
+
const metaResult = await getMeta(id);
|
| 39 |
+
meta = metaResult.meta;
|
| 40 |
+
metaCache.set(id, meta);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// Carica gli episodi solo se non sono già stati caricati
|
| 44 |
+
if (!meta.episodes) {
|
| 45 |
+
console.log(`Caricamento episodi per ${id}`);
|
| 46 |
+
meta.episodes = await getEpisodes(meta.seriesLink, meta.baseId);
|
| 47 |
+
metaCache.set(id, meta); // Aggiorna la cache con gli episodi
|
| 48 |
+
}
|
| 49 |
|
| 50 |
+
if (meta.episodes) {
|
| 51 |
return {
|
| 52 |
streams: meta.episodes?.flatMap(ep =>
|
| 53 |
ep.streams.map(stream => ({
|
|
|
|
| 56 |
type: "video/mp4",
|
| 57 |
behaviorHints: { bingeGroup: id }
|
| 58 |
}))
|
| 59 |
+
)
|
| 60 |
};
|
| 61 |
+
} else {
|
| 62 |
+
console.warn(`Nessun episodio trovato per ${id}`);
|
| 63 |
+
return { streams: [] };
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
} catch (error) {
|
| 67 |
console.error(`Handler Error: ${error.message}`);
|
| 68 |
return { streams: [] };
|
|
|
|
| 73 |
console.log("Catalog Handler chiamato con:", args); // Aggiungi questo log
|
| 74 |
if (args.type === 'series' && args.id === 'rama_series') {
|
| 75 |
return seriesCatalog(args);
|
| 76 |
+
|
| 77 |
+
}
|
| 78 |
return { metas: [] }; // Aggiungi un return di default
|
| 79 |
});
|
| 80 |
|
manifest.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
| 1 |
-
{
|
| 2 |
-
"id": "community.ramaorientalfansub",
|
| 3 |
-
"version": "1.0.6",
|
| 4 |
-
"name": "Rama Oriental Fansub",
|
| 5 |
-
"description": "Addon per visualizzare serie e film coreani dal sito Rama Oriental Fansub",
|
| 6 |
-
"catalogs": [
|
| 7 |
-
{
|
| 8 |
-
"type": "series",
|
| 9 |
-
"id": "rama_series",
|
| 10 |
-
"name": "Serie Coreane",
|
| 11 |
-
"extra": [{ "name": "skip" }]
|
| 12 |
-
}
|
| 13 |
-
|
| 14 |
-
],
|
| 15 |
-
"resources": ["catalog", "meta", "stream"],
|
| 16 |
-
"types": ["series"],
|
| 17 |
-
"logo": "https://
|
| 18 |
-
"background": "https://
|
| 19 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "community.ramaorientalfansub",
|
| 3 |
+
"version": "1.0.6",
|
| 4 |
+
"name": "Rama Oriental Fansub +",
|
| 5 |
+
"description": "Addon per visualizzare serie e film coreani dal sito Rama Oriental Fansub",
|
| 6 |
+
"catalogs": [
|
| 7 |
+
{
|
| 8 |
+
"type": "series",
|
| 9 |
+
"id": "rama_series",
|
| 10 |
+
"name": "Serie Coreane",
|
| 11 |
+
"extra": [{ "name": "skip" }]
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
],
|
| 15 |
+
"resources": ["catalog", "meta", "stream"],
|
| 16 |
+
"types": ["series"],
|
| 17 |
+
"logo": "https://i.imgur.com/i7VdVv7.png",
|
| 18 |
+
"background": "https://i.imgur.com/mtsxMk7.jpeg"
|
| 19 |
+
}
|
package-lock.json
CHANGED
|
@@ -16,6 +16,21 @@
|
|
| 16 |
"uuid": "^9.0.1"
|
| 17 |
}
|
| 18 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
"node_modules/tr46": {
|
| 20 |
"version": "0.0.3",
|
| 21 |
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
|
@@ -1750,16 +1765,6 @@
|
|
| 1750 |
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
| 1751 |
"license": "MIT"
|
| 1752 |
},
|
| 1753 |
-
"node_modules/request/node_modules/qs": {
|
| 1754 |
-
"version": "6.5.3",
|
| 1755 |
-
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
|
| 1756 |
-
"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
|
| 1757 |
-
"license": "BSD-3-Clause",
|
| 1758 |
-
"peer": true,
|
| 1759 |
-
"engines": {
|
| 1760 |
-
"node": ">=0.6"
|
| 1761 |
-
}
|
| 1762 |
-
},
|
| 1763 |
"node_modules/express": {
|
| 1764 |
"version": "4.21.2",
|
| 1765 |
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
|
@@ -2004,6 +2009,21 @@
|
|
| 2004 |
"url": "https://github.com/sponsors/ljharb"
|
| 2005 |
}
|
| 2006 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2007 |
"node_modules/send/node_modules/encodeurl": {
|
| 2008 |
"version": "1.0.2",
|
| 2009 |
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
|
@@ -2253,18 +2273,13 @@
|
|
| 2253 |
}
|
| 2254 |
},
|
| 2255 |
"node_modules/qs": {
|
| 2256 |
-
"version": "6.
|
| 2257 |
-
"resolved": "https://registry.npmjs.org/qs/-/qs-6.
|
| 2258 |
-
"integrity": "sha512-
|
| 2259 |
"license": "BSD-3-Clause",
|
| 2260 |
-
"
|
| 2261 |
-
"side-channel": "^1.0.6"
|
| 2262 |
-
},
|
| 2263 |
"engines": {
|
| 2264 |
"node": ">=0.6"
|
| 2265 |
-
},
|
| 2266 |
-
"funding": {
|
| 2267 |
-
"url": "https://github.com/sponsors/ljharb"
|
| 2268 |
}
|
| 2269 |
},
|
| 2270 |
"node_modules/restore-cursor": {
|
|
|
|
| 16 |
"uuid": "^9.0.1"
|
| 17 |
}
|
| 18 |
},
|
| 19 |
+
"node_modules/body-parser/node_modules/qs": {
|
| 20 |
+
"version": "6.13.0",
|
| 21 |
+
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
| 22 |
+
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
| 23 |
+
"license": "BSD-3-Clause",
|
| 24 |
+
"dependencies": {
|
| 25 |
+
"side-channel": "^1.0.6"
|
| 26 |
+
},
|
| 27 |
+
"engines": {
|
| 28 |
+
"node": ">=0.6"
|
| 29 |
+
},
|
| 30 |
+
"funding": {
|
| 31 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 32 |
+
}
|
| 33 |
+
},
|
| 34 |
"node_modules/tr46": {
|
| 35 |
"version": "0.0.3",
|
| 36 |
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
|
|
|
| 1765 |
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
| 1766 |
"license": "MIT"
|
| 1767 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1768 |
"node_modules/express": {
|
| 1769 |
"version": "4.21.2",
|
| 1770 |
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
|
|
|
| 2009 |
"url": "https://github.com/sponsors/ljharb"
|
| 2010 |
}
|
| 2011 |
},
|
| 2012 |
+
"node_modules/express/node_modules/qs": {
|
| 2013 |
+
"version": "6.13.0",
|
| 2014 |
+
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
| 2015 |
+
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
| 2016 |
+
"license": "BSD-3-Clause",
|
| 2017 |
+
"dependencies": {
|
| 2018 |
+
"side-channel": "^1.0.6"
|
| 2019 |
+
},
|
| 2020 |
+
"engines": {
|
| 2021 |
+
"node": ">=0.6"
|
| 2022 |
+
},
|
| 2023 |
+
"funding": {
|
| 2024 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 2025 |
+
}
|
| 2026 |
+
},
|
| 2027 |
"node_modules/send/node_modules/encodeurl": {
|
| 2028 |
"version": "1.0.2",
|
| 2029 |
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
|
|
|
| 2273 |
}
|
| 2274 |
},
|
| 2275 |
"node_modules/qs": {
|
| 2276 |
+
"version": "6.5.3",
|
| 2277 |
+
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
|
| 2278 |
+
"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
|
| 2279 |
"license": "BSD-3-Clause",
|
| 2280 |
+
"peer": true,
|
|
|
|
|
|
|
| 2281 |
"engines": {
|
| 2282 |
"node": ">=0.6"
|
|
|
|
|
|
|
|
|
|
| 2283 |
}
|
| 2284 |
},
|
| 2285 |
"node_modules/restore-cursor": {
|
rama_series.js
CHANGED
|
@@ -1,113 +1,121 @@
|
|
| 1 |
-
import cloudscraper from 'cloudscraper';
|
| 2 |
-
import * as cheerio from 'cheerio';
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
const userAgents = [
|
| 6 |
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
|
| 7 |
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0',
|
| 8 |
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15'
|
| 9 |
-
];
|
| 10 |
-
|
| 11 |
-
function getRandomHeaders() {
|
| 12 |
-
return {
|
| 13 |
-
'User-Agent': userAgents[Math.floor(Math.random() * userAgents.length)],
|
| 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 |
-
followAllRedirects: true,
|
| 25 |
-
maxRedirects: 2,
|
| 26 |
-
timeout: 10000,
|
| 27 |
-
});
|
| 28 |
-
return data;
|
| 29 |
-
} catch (error) {
|
| 30 |
-
console.error("Errore con Cloudscraper:", error);
|
| 31 |
-
return null;
|
| 32 |
-
}
|
| 33 |
-
}
|
| 34 |
-
|
| 35 |
-
const BASE_URL = 'https://ramaorientalfansub.tv/paese/corea-del-sud/';
|
| 36 |
-
const ITEMS_PER_PAGE =
|
| 37 |
-
const MAX_PAGES = 35;
|
| 38 |
-
const catalogCache = new Map();
|
| 39 |
-
|
| 40 |
-
async function getCatalog(skip = 0) {
|
| 41 |
-
const catalog = [];
|
| 42 |
-
let pageNumber = Math.floor(skip / ITEMS_PER_PAGE) + 1;
|
| 43 |
-
let itemsToLoad = ITEMS_PER_PAGE;
|
| 44 |
-
|
| 45 |
-
while (catalog.length < itemsToLoad && pageNumber <= MAX_PAGES) {
|
| 46 |
-
const pageUrl = `${BASE_URL}page/${pageNumber}/`;
|
| 47 |
-
let data;
|
| 48 |
-
|
| 49 |
-
if (catalogCache.has(pageUrl)) {
|
| 50 |
-
data = catalogCache.get(pageUrl);
|
| 51 |
-
} else {
|
| 52 |
-
try {
|
| 53 |
-
data = await fetchWithCloudscraper(pageUrl);
|
| 54 |
-
if (!data) {
|
| 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.
|
| 70 |
-
if (catalog.length >= itemsToLoad)
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
const
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
}
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
};
|
|
|
|
| 1 |
+
import cloudscraper from 'cloudscraper';
|
| 2 |
+
import * as cheerio from 'cheerio';
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
const userAgents = [
|
| 6 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
|
| 7 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0',
|
| 8 |
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15'
|
| 9 |
+
];
|
| 10 |
+
|
| 11 |
+
function getRandomHeaders() {
|
| 12 |
+
return {
|
| 13 |
+
'User-Agent': userAgents[Math.floor(Math.random() * userAgents.length)],
|
| 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 |
+
followAllRedirects: true,
|
| 25 |
+
maxRedirects: 2,
|
| 26 |
+
timeout: 10000,
|
| 27 |
+
});
|
| 28 |
+
return data;
|
| 29 |
+
} catch (error) {
|
| 30 |
+
console.error("Errore con Cloudscraper:", error);
|
| 31 |
+
return null;
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
const BASE_URL = 'https://ramaorientalfansub.tv/paese/corea-del-sud/';
|
| 36 |
+
const ITEMS_PER_PAGE = 25;
|
| 37 |
+
const MAX_PAGES = 35;
|
| 38 |
+
const catalogCache = new Map();
|
| 39 |
+
|
| 40 |
+
async function getCatalog(skip = 0) {
|
| 41 |
+
const catalog = [];
|
| 42 |
+
let pageNumber = Math.floor(skip / ITEMS_PER_PAGE) + 1;
|
| 43 |
+
let itemsToLoad = ITEMS_PER_PAGE;
|
| 44 |
+
|
| 45 |
+
while (catalog.length < itemsToLoad && pageNumber <= MAX_PAGES) {
|
| 46 |
+
const pageUrl = `${BASE_URL}page/${pageNumber}/`;
|
| 47 |
+
let data;
|
| 48 |
+
|
| 49 |
+
if (catalogCache.has(pageUrl)) {
|
| 50 |
+
data = catalogCache.get(pageUrl);
|
| 51 |
+
} else {
|
| 52 |
+
try {
|
| 53 |
+
data = await fetchWithCloudscraper(pageUrl);
|
| 54 |
+
if (!data) {
|
| 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.bg-gradient-to-t').each((index, element) => {
|
| 70 |
+
if (catalog.length >= itemsToLoad) return false;
|
| 71 |
+
|
| 72 |
+
// Recupera l'immagine del poster
|
| 73 |
+
const posterElement = $(element).find('img.object-cover');
|
| 74 |
+
let poster = posterElement.attr('data-src') || posterElement.attr('src');
|
| 75 |
+
if (!poster) {
|
| 76 |
+
console.warn(`Poster mancante per l'elemento ${index}`);
|
| 77 |
+
return true; // Continua il ciclo
|
| 78 |
+
}
|
| 79 |
+
// const poster = posterElement.attr('src');
|
| 80 |
+
const titleElement = $(element).find('a.text-sm.line-clamp-2.font-medium.leading-snug.lg\\:leading-normal');
|
| 81 |
+
const title = titleElement.text().trim();
|
| 82 |
+
const link = titleElement.attr('href');
|
| 83 |
+
// const poster = $(element).find('img.object-cover').attr('src');
|
| 84 |
+
// const poster = $(element).find('img.object-cover').attr('data-src');
|
| 85 |
+
const tagElement = $(element).find('div.text-xs.text-text-color.w-full.line-clamp-1.absolute.bottom-1.text-opacity-75 span.inline-block.md\\:mlb-3.uppercase');
|
| 86 |
+
const tagText = tagElement.text().trim().toLowerCase();
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
if (tagText.includes('tv')) { // Aggiungi questa condizione
|
| 91 |
+
|
| 92 |
+
if (title && link) {
|
| 93 |
+
const formattedTitle = title.replace(/\s+/g, '-').toLowerCase().replace(/[()]/g, '');
|
| 94 |
+
const meta = {
|
| 95 |
+
id: formattedTitle,
|
| 96 |
+
type: 'series',
|
| 97 |
+
name: title,
|
| 98 |
+
poster: poster || 'https://example.com/default-poster.jpg',
|
| 99 |
+
description: title,
|
| 100 |
+
imdbRating: "N/A",
|
| 101 |
+
released: 2024,
|
| 102 |
+
};
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
catalog.push(meta);
|
| 106 |
+
foundItemsOnPage++;
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
});
|
| 110 |
+
|
| 111 |
+
pageNumber++;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
return catalog;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
export default async function (args) {
|
| 118 |
+
const skip = args.extra?.skip || 0;
|
| 119 |
+
const metas = await getCatalog(skip);
|
| 120 |
+
return { metas };
|
| 121 |
};
|