test: Test için push edildi

This commit is contained in:
2026-01-25 17:32:58 +03:00
parent 7317edc88b
commit b35df53bef
7 changed files with 5447 additions and 2 deletions

View File

@@ -8,6 +8,7 @@ import mime from "mime-types";
import { fileURLToPath } from "url";
import { exec, spawn } from "child_process";
import crypto from "crypto"; // 🔒 basit token üretimi için
import puppeteer from "puppeteer";
import { getDiskSpace, getDownloadsSize } from "./utils/diskSpace.js";
import { createAuth } from "./modules/auth.js";
import { buildHealthReport, healthRouter } from "./modules/health.js";
@@ -119,6 +120,11 @@ const TURKANIME_MAX_EPISODES =
? Number(process.env.TURKANIME_MAX_EPISODES)
: 500;
const TURKANIME_DEBUG = process.env.TURKANIME_DEBUG === "1";
const MAILRU_DEBUG = process.env.MAILRU_DEBUG === "1";
const PUPPETEER_HEADLESS = process.env.PUPPETEER_HEADLESS !== "0";
const PUPPETEER_TIMEOUT = Number(process.env.PUPPETEER_TIMEOUT) || 30000;
const MAILRU_TIMEOUT = Number(process.env.MAILRU_TIMEOUT) || PUPPETEER_TIMEOUT;
const MAILRU_TRACE = process.env.MAILRU_TRACE === "1";
const TMDB_API_KEY = process.env.TMDB_API_KEY;
const TMDB_BASE_URL = "https://api.themoviedb.org/3";
const TMDB_IMG_BASE =
@@ -216,6 +222,655 @@ function logTurkanime(message) {
console.log(`Turkanime: ${message}`);
}
function logMailru(message) {
if (!MAILRU_DEBUG) return;
console.log(`Mailru: ${message}`);
}
function logMailruTrace(message) {
if (!MAILRU_TRACE) return;
console.log(`MailruTrace: ${message}`);
}
function isMailruVideoUrl(url) {
if (!url || typeof url !== "string") return false;
if (!/mail\.ru/i.test(url)) return false;
return /\/video\/|\/video\/meta\/|\/video\/embed\/|\/embed\/|videoapi\.my\.mail\.ru/i.test(
url
);
}
function extractIframeSrcFromHtml(html) {
if (!html || typeof html !== "string") return null;
const match = html.match(/<iframe[^>]+src=["']([^"']+)["']/i);
return match ? match[1] : null;
}
function normalizeProviderText(text) {
if (!text) return "";
return String(text).replace(/[^a-z0-9]/gi, "").toUpperCase();
}
function isMailProviderText(text) {
const compact = normalizeProviderText(text);
if (!compact) return false;
if (compact === "MAIL" || compact === "MAILRU") return true;
return compact.startsWith("MAIL");
}
// Puppeteer browser instance'ı yeniden kullanmak için
let browserInstance = null;
let browserLaunchPromise = null;
async function getBrowser() {
if (browserInstance && browserInstance.isConnected()) {
return browserInstance;
}
if (browserLaunchPromise) {
return browserLaunchPromise;
}
browserLaunchPromise = (async () => {
try {
logMailru("Puppeteer başlatılıyor...");
// Docker container içinde sistem Chromium'unu kullan
// Yerel geliştirme ortamında Puppeteer kendi Chromium'unu kullanır
const isDocker = fs.existsSync('/.dockerenv');
const launchOptions = {
headless: PUPPETEER_HEADLESS ? "new" : false,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--disable-gpu',
'--disable-features=SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure',
'--autoplay-policy=no-user-gesture-required',
'--window-size=1920x1080'
]
};
// Docker'da sistem Chromium'unu kullan
if (isDocker || process.env.PUPPETEER_EXECUTABLE_PATH) {
launchOptions.executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || '/usr/bin/chromium';
logMailru(`Docker tespit edildi, sistem Chromium kullanılacak: ${launchOptions.executablePath}`);
}
browserInstance = await puppeteer.launch(launchOptions);
logMailru("Puppeteer başarıyla başlatıldı");
return browserInstance;
} catch (err) {
logMailru(`Puppeteer başlatılamadı: ${err.message}`);
browserLaunchPromise = null;
throw err;
}
})();
return browserLaunchPromise;
}
async function extractMailruData(turkanimeVideoUrl) {
if (!turkanimeVideoUrl || typeof turkanimeVideoUrl !== "string") {
throw new Error("Geçerli bir Turkanime video URL'si gerekli.");
}
const url = turkanimeVideoUrl.trim();
const urlObj = new URL(url);
const host = urlObj.hostname.toLowerCase();
if (!TURKANIME_HOSTS.has(host)) {
throw new Error("Sadece turkanime.tv domain'inden linkler destekleniyor.");
}
if (!urlObj.pathname.startsWith("/video/")) {
throw new Error("Geçersiz URL formatı. /video/ path'i gerekli.");
}
logMailru(`Mail.ru URL ayıklanıyor: ${url}`);
let page;
let browser;
let requestTimeout = null;
let cdp = null;
try {
browser = await getBrowser();
page = await browser.newPage();
cdp = await page.target().createCDPSession();
await cdp.send('Network.enable');
// User-agent ve gerekli header'lar
await page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
await page.setViewport({ width: 1920, height: 1080 });
const findMailruInPage = async () => {
const mailruUrlFromDom = await page.evaluate(() => {
// 1. Tüm linkleri kontrol et
const links = document.querySelectorAll('a');
for (const link of links) {
const href = link.href || '';
if (href.includes('mail.ru')) {
return href;
}
}
// 2. Tüm iframe'leri kontrol et
const iframes = document.querySelectorAll('iframe');
for (const iframe of iframes) {
const src = iframe.src || '';
if (src.includes('mail.ru')) {
return src;
}
}
// 3. window.location'ta mail.ru var mı kontrol et
if (window.location.href.includes('mail.ru')) {
return window.location.href;
}
// 4. Tüm script etiketlerini kontrol et
const scripts = document.querySelectorAll('script');
for (const script of scripts) {
const content = script.textContent || '';
const match = content.match(/(https?:\/\/[^"'\s<]*mail\.ru\/[^"'\s<]+)/i);
if (match) {
return match[1];
}
}
// 5. Sayfa içeriğinde ara (decoded URL)
const bodyText = document.body.innerHTML || document.body.textContent || '';
const match = bodyText.match(/(https?:\/\/[^"'\s<]*mail\.ru\/[^"'\s<]+)/i);
if (match) {
return match[1];
}
return null;
});
if (mailruUrlFromDom) return mailruUrlFromDom;
const pageHtml = await page.content();
const mailruMatch = pageHtml.match(/(https?:\/\/[^"'\s<]*mail\.ru\/[^"'\s<]+)/i);
return mailruMatch ? mailruMatch[1] : null;
};
const waitForMailruMeta = async () => {
try {
const response = await page.waitForResponse(
res =>
res.url().includes('mail.ru') &&
res.url().includes('/video/meta/'),
{ timeout: MAILRU_TIMEOUT }
);
let metaJson = null;
try {
metaJson = await response.json();
} catch (e) {
const text = await response.text();
metaJson = text;
}
return { url: response.url(), meta: metaJson };
} catch {
return null;
}
};
const waitMs = ms => new Promise(resolve => setTimeout(resolve, ms));
const tryClickPlay = async () => {
try {
await page.evaluate(() => {
const selectors = [
'button[aria-label*="Play"]',
'button[title*="Play"]',
'.vjs-big-play-button',
'.jw-icon-playback',
'.plyr__control',
'.plyr__control--overlaid',
'.vjs-big-play-button .vjs-icon-placeholder'
];
for (const sel of selectors) {
const el = document.querySelector(sel);
if (el) {
el.click();
return true;
}
}
return false;
});
} catch (e) {
// Yoksay
}
};
// Request/Response interceptor - mail.ru URL'ini yakalamak için
// Promise'i sayfa yüklemeden ÖNCE kur
const mailruUrlPromise = new Promise((resolve) => {
let resolved = false;
requestTimeout = setTimeout(() => {
if (!resolved) {
resolved = true;
resolve(null);
}
}, MAILRU_TIMEOUT);
// Response dinleyicisi - mail.ru yanıtlarını yakala
page.on('response', async (response) => {
if (resolved) return;
try {
const responseUrl = response.url();
// Mail.ru video bağlantılarını yakala
if (isMailruVideoUrl(responseUrl)) {
resolved = true;
clearTimeout(requestTimeout);
logMailru(`Mail.ru URL bulundu (response): ${responseUrl}`);
resolve(responseUrl);
}
if (MAILRU_TRACE && responseUrl.includes('mail.ru')) {
logMailruTrace(`response: ${responseUrl}`);
}
} catch (err) {
// Response hatası yoksay
}
});
// Request dinleyicisi - mail.ru request'lerini de yakala
page.on('request', (request) => {
if (resolved) return;
try {
const requestUrl = request.url();
// Mail.ru video bağlantılarını yakala
if (isMailruVideoUrl(requestUrl)) {
resolved = true;
clearTimeout(requestTimeout);
logMailru(`Mail.ru URL bulundu (request): ${requestUrl}`);
resolve(requestUrl);
}
if (MAILRU_TRACE && requestUrl.includes('mail.ru')) {
logMailruTrace(`request: ${requestUrl}`);
}
} catch (err) {
// Request hatası yoksay
}
});
});
const mailruMetaPromise = waitForMailruMeta();
const mailruCdpPromise = new Promise(resolve => {
let done = false;
cdp.on('Network.responseReceived', async (event) => {
if (done) return;
const responseUrl = event?.response?.url || '';
if (responseUrl.includes('mail.ru') && responseUrl.includes('/video/meta/')) {
done = true;
try {
const body = await cdp.send('Network.getResponseBody', {
requestId: event.requestId
});
let meta = null;
try {
meta = JSON.parse(body.body);
} catch {
meta = body.body;
}
resolve({ url: responseUrl, meta });
} catch (e) {
resolve({ url: responseUrl, meta: null });
}
} else if (MAILRU_TRACE && responseUrl.includes('mail.ru')) {
logMailruTrace(`cdp response: ${responseUrl}`);
}
});
});
if (MAILRU_TRACE) {
page.on('requestfailed', request => {
const url = request.url();
if (url.includes('mail.ru')) {
logMailruTrace(`request failed: ${url} (${request.failure()?.errorText || 'unknown'})`);
}
});
}
logMailru("Sayfa yükleniyor...");
await page.goto(url, {
waitUntil: 'networkidle2',
timeout: PUPPETEER_TIMEOUT
});
const metaEarly = await Promise.race([mailruMetaPromise, mailruCdpPromise, waitMs(1000)]);
if (metaEarly && metaEarly.url) {
logMailru(`Mail.ru meta yanıtı bulundu: ${metaEarly.url}`);
return { mailruUrl: metaEarly.url, mailruMeta: metaEarly.meta };
}
logMailru("İlk DOM taraması yapılıyor...");
const initialMailru = await findMailruInPage();
if (initialMailru) {
logMailru(`Mail.ru URL ilk taramada bulundu: ${initialMailru}`);
clearTimeout(requestTimeout);
return { mailruUrl: initialMailru, mailruMeta: null };
}
logMailru("MAIL butonu aranıyor...");
// Tüm butonları ve linkleri kontrol et - daha güvenilir yöntem
const buttons = await page.$$('button, a');
let mailButton = null;
logMailru(`${buttons.length} adet buton bulundu, taranıyor...`);
const candidates = [];
for (let i = 0; i < buttons.length; i++) {
const btn = buttons[i];
try {
const text = await page.evaluate(el => (el.textContent || el.innerText || '').trim(), btn);
const onclick = await page.evaluate(el => el.getAttribute('onclick') || '', btn);
const onclickLower = onclick.toLowerCase();
if (onclickLower.includes('indexicerik')) {
candidates.push({ index: i, btn, text, onclick });
}
} catch (e) {
// Devam et
}
}
for (const c of candidates) {
if (isMailProviderText(c.text)) {
mailButton = c.btn;
logMailru(`MAIL butonu bulundu (index: ${c.index}): ${c.text}`);
break;
}
}
if (!mailButton && candidates.length) {
logMailru("MAIL text eşleşmesi yok; IndexIcerik adaylarından devam edilecek.");
}
const videosecResponsePromise = page
.waitForResponse(res => res.url().includes('/ajax/videosec'), { timeout: 8000 })
.catch(() => null);
if (!mailButton) {
logMailru("MAIL butonu bulunamadı. Fallback akışları deneniyor...");
} else {
// Butona tıkla
logMailru("MAIL butonuna tıklanıyor...");
// Önce onclick'teki URL'i al
const onclickValue = await page.evaluate(el => el.getAttribute('onclick'), mailButton);
logMailru(`Onclick değeri: ${onclickValue ? onclickValue.substring(0, 100) : 'yok'}`);
// Tıkla
await mailButton.click();
}
// Ajax sonrası iframe'in yüklenmesini bekle
logMailru("Ajax sonrası iframe yükleniyor...");
// AJAX cevabını yakala ve iframe src'yi çıkar
try {
const videosecResponse = await videosecResponsePromise;
if (videosecResponse) {
const videosecHtml = await videosecResponse.text();
const iframeSrcFromAjax = extractIframeSrcFromHtml(videosecHtml);
if (iframeSrcFromAjax) {
const embedUrl = iframeSrcFromAjax.startsWith('//')
? 'https:' + iframeSrcFromAjax
: iframeSrcFromAjax;
logMailru(`videosec içinden embed URL bulundu: ${embedUrl.substring(0, 100)}...`);
// iframe src'yi sayfada set ederek aynı sayfa içinde kal
try {
await page.waitForSelector('#videodetay iframe', { timeout: 10000 });
await page.evaluate((src) => {
const iframe = document.querySelector('#videodetay iframe');
if (iframe && (!iframe.src || iframe.src === "about:blank")) {
iframe.src = src;
}
}, embedUrl);
} catch (e) {
// yoksay
}
await tryClickPlay();
const metaAfterEmbed = await Promise.race([waitForMailruMeta(), mailruCdpPromise, waitMs(15000)]);
if (metaAfterEmbed && metaAfterEmbed.url) {
logMailru(`Mail.ru meta yanıtı iframe (aynı sayfa) sonrası bulundu: ${metaAfterEmbed.url}`);
return { mailruUrl: metaAfterEmbed.url, mailruMeta: metaAfterEmbed.meta };
}
}
}
} catch (e) {
// videosec bulunamazsa DOM akışı devam etsin
}
// Biraz bekle - ajax'ın tamamlanması için
await new Promise(resolve => setTimeout(resolve, 2000));
// #videodetay içindeki iframe'i bekle ve src'sini al
try {
if (page.url().includes('/video/')) {
try {
await page.waitForSelector('#videodetay iframe', { timeout: 10000 });
logMailru("iframe bulundu!");
} catch (e) {
logMailru("videodetay iframe bulunamadı, devam ediliyor...");
}
}
let iframeSrc = null;
// İframe src'sini al (video sayfası)
iframeSrc = await page.evaluate(() => {
const iframe = document.querySelector('#videodetay iframe');
return iframe ? iframe.src : null;
});
logMailru(`iframe src: ${iframeSrc ? iframeSrc.substring(0, 100) : 'yok'}`);
if (!iframeSrc) {
throw new Error("iframe src bulunamadı");
}
// Embed sayfası bir SPA - JavaScript ile içerik yüklüyor
// JavaScript'in tam yüklenmesini bekle
logMailru("Embed sayfasında JavaScript yükleniyor...");
// Video player'ın yüklenmesini bekle (önce iframe ara)
let iframeElement = null;
let videoElement = null;
try {
// Önce iframe'i ara
iframeElement = await page.waitForSelector('iframe', { timeout: 10000 });
logMailru("İframe bulundu!");
} catch (e) {
logMailru("İframe timeout, video elementi aranıyor...");
try {
videoElement = await page.waitForSelector('video', { timeout: 5000 });
logMailru("Video elementi bulundu!");
try {
await page.evaluate(el => {
try {
el && el.play && el.play();
} catch (e) {
// Yoksay
}
}, videoElement);
} catch (e) {
// Yoksay
}
} catch (e2) {
logMailru("Video elementi de bulunamadı, devam ediliyor...");
}
}
// İframe varsa, içine gir
if (iframeElement) {
const iframeSrcInner = await page.evaluate(iframe => iframe.src, iframeElement);
logMailru(`İçteki iframe src: ${iframeSrcInner ? iframeSrcInner.substring(0, 100) : 'yok'}`);
// İçteki iframe mail.ru ise, src'sini al
if (iframeSrcInner && iframeSrcInner.includes('mail.ru')) {
logMailru(`Mail.ru URL içteki iframe'de bulundu: ${iframeSrcInner}`);
clearTimeout(requestTimeout);
return { mailruUrl: iframeSrcInner, mailruMeta: null };
}
const iframeContent = await iframeElement.contentFrame();
if (iframeContent) {
try {
await iframeContent.evaluate(() => {
const v = document.querySelector('video');
if (v && v.play) {
v.play().catch(() => {});
}
});
} catch (e) {
// Yoksay
}
// İframe içinde mail.ru isteğini bekle
try {
const metaFromFrame = await Promise.race([
page.waitForResponse(
res =>
res.url().includes('mail.ru') &&
res.url().includes('/video/meta/') &&
res.frame() === iframeContent,
{ timeout: MAILRU_TIMEOUT }
),
mailruCdpPromise,
waitMs(MAILRU_TIMEOUT)
]);
if (metaFromFrame && metaFromFrame.url) {
let metaJson = null;
try {
if (metaFromFrame.json) {
metaJson = await metaFromFrame.json();
} else if (metaFromFrame.meta) {
metaJson = metaFromFrame.meta;
}
} catch (e) {
if (metaFromFrame.text) {
metaJson = await metaFromFrame.text();
} else {
metaJson = metaFromFrame.meta || null;
}
}
const metaUrl = metaFromFrame.url ? metaFromFrame.url() : metaFromFrame.url;
logMailru(`Mail.ru meta yanıtı iframe içinde bulundu: ${metaUrl}`);
return { mailruUrl: metaUrl, mailruMeta: metaJson };
}
} catch (e) {
// Yoksay
}
// İframe içinde mail.ru ara
const mailruInIframe = await iframeContent.evaluate(() => {
// Tüm linkler
const links = document.querySelectorAll('a');
for (const link of links) {
if (link.href && link.href.includes('mail.ru')) {
return link.href;
}
}
// Tüm iframe'ler
const iframes = document.querySelectorAll('iframe');
for (const iframe of iframes) {
if (iframe.src && iframe.src.includes('mail.ru')) {
return iframe.src;
}
}
// Body içinde ara
const bodyText = document.body.innerHTML || document.body.textContent || '';
const match = bodyText.match(/(https?:\/\/[^"'\s<]*mail\.ru\/[^"'\s<]+)/i);
if (match) {
return match[1];
}
return null;
});
if (mailruInIframe) {
logMailru(`Mail.ru URL iframe içinden bulundu: ${mailruInIframe}`);
clearTimeout(requestTimeout);
return { mailruUrl: mailruInIframe, mailruMeta: null };
}
}
}
// Ekstra bekle - SPA için
await new Promise(resolve => setTimeout(resolve, 5000));
// Video oynatmayı tetikle (bazı sayfalarda meta isteği ancak etkileşim sonrası gelir)
try {
await tryClickPlay();
const metaAfterPlay = await Promise.race([waitForMailruMeta(), waitMs(15000)]);
if (metaAfterPlay && metaAfterPlay.url) {
logMailru(`Mail.ru meta yanıtı play sonrası bulundu: ${metaAfterPlay.url}`);
return { mailruUrl: metaAfterPlay.url, mailruMeta: metaAfterPlay.meta };
}
} catch (e) {
// Yoksay
}
// JavaScript ile DOM'da mail.ru linkini ara
logMailru("DOM'da mail.ru URL'si aranıyor...");
const mailruUrlFromDom = await findMailruInPage();
if (mailruUrlFromDom) {
logMailru(`Mail.ru URL DOM'dan bulundu: ${mailruUrlFromDom}`);
clearTimeout(requestTimeout);
return { mailruUrl: mailruUrlFromDom, mailruMeta: null };
}
logMailru("DOM/HTML'de mail.ru bulunamadı");
} catch (e) {
logMailru(`iframe işleme hatası: ${e.message}`);
}
// Mail.ru URL'sini bekle (orijinal yöntem fallback)
logMailru("Mail.ru URL'si bekleniyor (request listener)...");
const mailruMetaResult = await Promise.race([mailruMetaPromise, mailruCdpPromise]);
if (mailruMetaResult && mailruMetaResult.url) {
return { mailruUrl: mailruMetaResult.url, mailruMeta: mailruMetaResult.meta };
}
const mailruUrl = await mailruUrlPromise;
return { mailruUrl, mailruMeta: null };
} finally {
if (requestTimeout) {
clearTimeout(requestTimeout);
}
if (page) {
await page.close().catch(err => logMailru(`Page kapatma hatası: ${err.message}`));
}
if (cdp) {
await cdp.detach().catch(() => {});
}
}
}
// Turkanime video URL'sini parse et
function parseTurkanimeVideoUrl(rawUrl) {
if (!rawUrl || typeof rawUrl !== "string") return null;
try {
const url = new URL(rawUrl.trim());
const host = url.hostname.toLowerCase();
if (!TURKANIME_HOSTS.has(host)) return null;
const pathname = url.pathname.replace(/\/+$/, "");
const match = pathname.match(/^\/video\/([^/]+)$/);
if (!match) return null;
const slug = match[1]?.trim();
return slug ? `https://www.turkanime.tv/video/${slug}` : null;
} catch {
return null;
}
}
function isTurkanimeNotFound(html) {
if (!html) return false;
return (
@@ -6591,6 +7246,49 @@ app.post("/api/turkanime/episodes", requireAuth, async (req, res) => {
res.json({ ok: true, slug, count: episodes.length, episodes });
});
// --- 📧 Turkanime video sayfasından mail.ru linki ayıklama ---
app.post("/api/turkanime/mailru", requireAuth, async (req, res) => {
try {
const rawUrl = req.body?.url;
const normalizedUrl = parseTurkanimeVideoUrl(rawUrl);
if (!normalizedUrl) {
return res.status(400).json({
ok: false,
error: "Geçerli bir Turkanime video URL'si gerekli. (örn: https://www.turkanime.tv/video/07-ghost-1-bolum)"
});
}
logMailru(`Mail.ru linki ayıklanıyor: ${normalizedUrl}`);
const mailruData = await extractMailruData(normalizedUrl);
const mailruUrl = mailruData?.mailruUrl;
if (!mailruUrl) {
return res.status(404).json({
ok: false,
error: "Mail.ru linki bulunamadı. Sayfa yapısı değişmiş olabilir."
});
}
logMailru(`Mail.ru linki başarıyla ayıklandı: ${mailruUrl}`);
res.json({
ok: true,
turkanimeUrl: normalizedUrl,
mailruUrl: mailruUrl,
mailruMeta: mailruData?.mailruMeta || null
});
} catch (err) {
logMailru(`Hata: ${err?.message || err}`);
res.status(500).json({
ok: false,
error: err?.message || "Mail.ru linki ayıklanamadı."
});
}
});
app.post("/api/youtube/download", requireAuth, async (req, res) => {
try {
const rawUrl = req.body?.url;