feat(anime): turkanime bölüm listesi çekme özelliği ekle

Turkanime anime URL'lerini destekleyerek bölüm listelerini çekme
özelliği eklendi. Kullanıcılar turkanime.tv anime sayfa URL'lerini
girerek bölüm linklerini alabilirler.

- Yeni TURKANIME_DEBUG çevre değişkeni eklendi
- /api/turkanime/episodes API uç noktası eklendi
- İstemci tarafında URL normalizasyonu ve işlemesi eklendi
- HTML'den bölüm linklerini çıkarma mantığı eklendi
This commit is contained in:
2026-01-22 13:32:19 +03:00
parent 1bad4f7256
commit 511a8cbba0
3 changed files with 155 additions and 2 deletions

View File

@@ -31,3 +31,6 @@ AUTO_PAUSE_ON_COMPLETE=0
# Medya işleme adımlarını (ffprobe/ffmpeg, thumbnail ve TMDB/TVDB metadata) devre dışı bırakır; # Medya işleme adımlarını (ffprobe/ffmpeg, thumbnail ve TMDB/TVDB metadata) devre dışı bırakır;
# CPU ve disk kullanımını düşürür, ancak kapalıyken medya bilgileri eksik kalır. # CPU ve disk kullanımını düşürür, ancak kapalıyken medya bilgileri eksik kalır.
DISABLE_MEDIA_PROCESSING=0 DISABLE_MEDIA_PROCESSING=0
# Turkanime bölüm listesi çekme işlemlerinde ayrıntılı backend loglarını açar.
# Sorun tespiti için 1 yapın, normal kullanımda 0 bırakın.
TURKANIME_DEBUG=0

View File

@@ -98,8 +98,23 @@
} }
} }
function normalizeTurkanimeUrl(value) {
if (!value || typeof value !== "string") return null;
try {
const url = new URL(value.trim());
if (url.protocol !== "https:") return null;
const host = url.hostname.toLowerCase();
if (!host.endsWith("turkanime.tv")) return null;
if (!url.pathname.startsWith("/anime/")) return null;
url.hash = "";
return url.toString();
} catch {
return null;
}
}
async function handleUrlInput() { async function handleUrlInput() {
const input = prompt("Magnet veya YouTube URL girin:"); const input = prompt("Magnet, YouTube veya turkanime URL girin:");
if (!input) return; if (!input) return;
if (isMagnetLink(input)) { if (isMagnetLink(input)) {
await apiFetch("/api/transfer", { await apiFetch("/api/transfer", {
@@ -110,6 +125,23 @@
await list(); await list();
return; return;
} }
const normalizedTurkanime = normalizeTurkanimeUrl(input);
if (normalizedTurkanime) {
const resp = await apiFetch("/api/turkanime/episodes", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: normalizedTurkanime })
});
if (!resp.ok) {
const data = await resp.json().catch(() => null);
alert(data?.error || "Turkanime bölümleri alınamadı.");
return;
}
const data = await resp.json().catch(() => null);
const links = Array.isArray(data?.links) ? data.links : [];
alert(links.join("\n"));
return;
}
const normalizedYoutube = normalizeYoutubeUrl(input); const normalizedYoutube = normalizeYoutubeUrl(input);
if (normalizedYoutube) { if (normalizedYoutube) {
const resp = await apiFetch("/api/youtube/download", { const resp = await apiFetch("/api/youtube/download", {
@@ -126,7 +158,7 @@
return; return;
} }
alert( alert(
"Yalnızca magnet linkleri veya https://www.youtube.com/watch?v=... formatındaki YouTube URL'leri destekleniyor." "Yalnızca magnet linkleri, YouTube veya turkanime anime URL'leri destekleniyor."
); );
} }

View File

@@ -91,6 +91,7 @@ const YT_ALLOWED_RESOLUTIONS = new Set([
]); ]);
const YT_EXTRACTOR_ARGS = const YT_EXTRACTOR_ARGS =
process.env.YT_DLP_EXTRACTOR_ARGS || null; process.env.YT_DLP_EXTRACTOR_ARGS || null;
const TURKANIME_DEBUG = String(process.env.TURKANIME_DEBUG || "").toLowerCase() === "1";
let resolvedYtDlpBinary = null; let resolvedYtDlpBinary = null;
const TMDB_API_KEY = process.env.TMDB_API_KEY; const TMDB_API_KEY = process.env.TMDB_API_KEY;
const TMDB_BASE_URL = "https://api.themoviedb.org/3"; const TMDB_BASE_URL = "https://api.themoviedb.org/3";
@@ -728,6 +729,52 @@ function normalizeYoutubeWatchUrl(value) {
} }
} }
function normalizeTurkanimeUrl(value) {
if (!value || typeof value !== "string") return null;
try {
const urlObj = new URL(value.trim());
if (urlObj.protocol !== "https:") return null;
const host = urlObj.hostname.toLowerCase();
if (!host.endsWith("turkanime.tv")) return null;
if (!urlObj.pathname.startsWith("/anime/")) return null;
urlObj.hash = "";
return urlObj.toString();
} catch (err) {
return null;
}
}
function extractTurkanimeEpisodeLinks(html) {
if (!html) return [];
const listMatch = html.match(
/<ul[^>]*class=["'][^"']*list[^"']*menum[^"']*["'][^>]*>([\s\S]*?)<\/ul>/i
);
if (!listMatch) return [];
const listHtml = listMatch[1];
const hrefs = [];
const hrefRegex = /href=["']([^"']+)["']/gi;
let match;
while ((match = hrefRegex.exec(listHtml))) {
hrefs.push(match[1]);
}
const links = hrefs
.filter((href) => href.includes("/video/"))
.map((href) => {
if (href.startsWith("//")) return `https:${href}`;
if (href.startsWith("/")) return `https://www.turkanime.tv${href}`;
return href;
})
.filter((href) => {
try {
const urlObj = new URL(href);
return urlObj.hostname.toLowerCase().endsWith("turkanime.tv");
} catch {
return false;
}
});
return Array.from(new Set(links));
}
function startYoutubeDownload(url) { function startYoutubeDownload(url) {
const normalized = normalizeYoutubeWatchUrl(url); const normalized = normalizeYoutubeWatchUrl(url);
if (!normalized) return null; if (!normalized) return null;
@@ -6234,6 +6281,77 @@ app.post("/api/youtube/download", requireAuth, async (req, res) => {
} }
}); });
app.post("/api/turkanime/episodes", requireAuth, async (req, res) => {
try {
const rawUrl = req.body?.url;
const normalized = normalizeTurkanimeUrl(rawUrl);
if (!normalized) {
return res.status(400).json({
ok: false,
error: "Geçerli bir turkanime.tv anime URL'si gerekli."
});
}
if (TURKANIME_DEBUG) {
console.log("🧪 Turkanime fetch başlıyor:", normalized);
}
const resp = await fetch(normalized, {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
Accept: "text/html"
}
});
if (!resp.ok) {
if (TURKANIME_DEBUG) {
console.warn("🧪 Turkanime HTTP hata:", resp.status, resp.statusText);
}
return res.status(502).json({
ok: false,
error: `Turkanime sayfası alınamadı (HTTP ${resp.status}).`
});
}
const html = await resp.text();
if (TURKANIME_DEBUG) {
console.log("🧪 Turkanime HTML uzunluğu:", html.length);
}
const links = extractTurkanimeEpisodeLinks(html);
if (!links.length) {
if (TURKANIME_DEBUG) {
const hasBolumler = html.includes("bolumler");
const hasList = /class=["'][^"']*list[^"']*menum/i.test(html);
const bolumIndex = html.indexOf("bolumler");
const snippetStart = bolumIndex > 200 ? bolumIndex - 200 : 0;
const snippet =
bolumIndex >= 0
? html.slice(snippetStart, bolumIndex + 400)
: html.slice(0, 400);
console.warn("🧪 Turkanime bölüm listesi bulunamadı.", {
hasBolumler,
hasList,
bolumIndex,
snippet
});
}
return res.status(404).json({
ok: false,
error: "Bölüm listesi bulunamadı."
});
}
if (TURKANIME_DEBUG) {
console.log("🧪 Turkanime bölüm link sayısı:", links.length);
}
res.json({ ok: true, links });
} catch (err) {
if (TURKANIME_DEBUG) {
console.error("🧪 Turkanime hata:", err?.message || err);
}
res.status(500).json({
ok: false,
error: err?.message || "Turkanime verisi alınamadı."
});
}
});
// --- 🎫 YouTube cookies yönetimi --- // --- 🎫 YouTube cookies yönetimi ---
app.get("/api/youtube/cookies", requireAuth, (req, res) => { app.get("/api/youtube/cookies", requireAuth, (req, res) => {
try { try {