Files
dupe/client/src/routes/Settings.svelte
szbk 2f58ef5ef9 feat(youtube): youtube çerez yönetimi ekle
YouTube cookies.txt dosyasını yönetmek için ayarlar arayüzü ve API uç noktaları eklendi.
- Kullanıcı arayüzünde çerez yükleme/kaydetme fonksiyonalitesi
- Cookies.txt dosyası için GET/POST API uç noktaları
- YouTube indirme işlemlerinde çerez desteği
- Çerez dosyası boyutu ve karakter validasyonu
- Ayarlar sayfasında sekme tabanlı arayüz
2025-12-14 15:33:56 +03:00

302 lines
6.5 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
import { onMount } from "svelte";
import { apiFetch } from "../utils/api.js";
const tabs = [
{ id: "general", label: "General", icon: "fa-solid fa-sliders" },
{ id: "youtube", label: "YouTube", icon: "fa-brands fa-youtube" },
{ id: "advanced", label: "Advanced", icon: "fa-solid fa-gear" }
];
let activeTab = "youtube";
let youtubeCookies = "";
let cookiesUpdatedAt = null;
let loadingCookies = false;
let savingCookies = false;
let error = null;
let success = null;
async function loadCookies() {
loadingCookies = true;
error = null;
success = null;
try {
const resp = await apiFetch("/api/youtube/cookies");
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const data = await resp.json();
youtubeCookies = data?.cookies || "";
cookiesUpdatedAt = data?.updatedAt || null;
} catch (err) {
error = err?.message || "Cookies alınamadı.";
} finally {
loadingCookies = false;
}
}
async function saveCookies() {
if (savingCookies) return;
error = null;
success = null;
savingCookies = true;
try {
const payload = {
cookies: youtubeCookies
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
.join("\n")
};
const resp = await apiFetch("/api/youtube/cookies", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
const data = await resp.json().catch(() => ({}));
if (!resp.ok || !data?.ok) {
throw new Error(data?.error || `HTTP ${resp.status}`);
}
cookiesUpdatedAt = data.updatedAt || Date.now();
success = "Cookies kaydedildi.";
} catch (err) {
error = err?.message || "Cookies kaydedilemedi.";
} finally {
savingCookies = false;
}
}
onMount(() => {
loadCookies();
});
function formatDate(ts) {
if (!ts) return "—";
const d = new Date(Number(ts));
if (Number.isNaN(d.getTime())) return "—";
return d.toLocaleString();
}
</script>
<section class="files">
<div class="files-header">
<div class="header-title">
<h2>Settings</h2>
</div>
</div>
<div class="tabs">
{#each tabs as tab}
<button
class:active={activeTab === tab.id}
class="tab"
type="button"
on:click={() => (activeTab = tab.id)}
>
<i class={tab.icon}></i>
<span>{tab.label}</span>
</button>
{/each}
</div>
{#if activeTab === "youtube"}
<div class="card">
<div class="card-header">
<div class="title">
<i class="fa-brands fa-youtube"></i>
<span>YouTube Cookies</span>
</div>
{#if cookiesUpdatedAt}
<div class="meta">Son güncelleme: {formatDate(cookiesUpdatedAt)}</div>
{/if}
</div>
<div class="field">
<label for="cookies">cookies.txt içeriği</label>
<textarea
id="cookies"
spellcheck="false"
placeholder="Netscape HTTP Cookie formatında (cookies.txt) içerik girin"
bind:value={youtubeCookies}
></textarea>
<small>
Zararlı komut çalıştırılamaz; yalnızca düz metin cookie satırları yazılır.
Maksimum 20KB. Engellenen karakterler otomatik reddedilir.
</small>
</div>
<div class="actions">
<button class="btn" on:click={loadCookies} disabled={loadingCookies || savingCookies}>
<i class="fa-solid fa-rotate"></i> Yenile
</button>
<button class="btn primary" on:click={saveCookies} disabled={loadingCookies || savingCookies}>
<i class="fa-solid fa-floppy-disk"></i> Kaydet
</button>
</div>
{#if error}
<div class="alert error">
<i class="fa-solid fa-circle-exclamation"></i>
{error}
</div>
{/if}
{#if success}
<div class="alert success">
<i class="fa-solid fa-circle-check"></i>
{success}
</div>
{/if}
</div>
{:else if activeTab === "general"}
<div class="card muted">Genel ayarlar burada yer alacak.</div>
{:else if activeTab === "advanced"}
<div class="card muted">Gelişmiş ayarlar burada yer alacak.</div>
{/if}
</section>
<style>
.files {
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.files-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.header-title {
display: flex;
align-items: center;
gap: 8px;
}
.tabs {
display: flex;
gap: 8px;
}
.tab {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
border: 1px solid var(--border, #dcdcdc);
border-radius: 8px;
background: #f7f7f7;
cursor: pointer;
color: #333;
}
.tab.active {
background: #222;
color: #fff;
border-color: #222;
}
.card {
border: 1px solid var(--border, #e0e0e0);
border-radius: 10px;
padding: 14px;
background: #fff;
display: flex;
flex-direction: column;
gap: 12px;
}
.card.muted {
color: #777;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
}
.card-header .title {
display: flex;
align-items: center;
gap: 8px;
font-weight: 700;
}
.card-header .meta {
color: #666;
font-size: 12px;
}
.field {
display: flex;
flex-direction: column;
gap: 6px;
}
.field label {
font-weight: 600;
}
.field textarea {
min-height: 180px;
padding: 10px;
border: 1px solid var(--border, #dcdcdc);
border-radius: 8px;
font-family: monospace;
font-size: 13px;
resize: vertical;
}
.field small {
color: #666;
}
.actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
border: 1px solid var(--border, #dcdcdc);
border-radius: 8px;
background: #f7f7f7;
cursor: pointer;
}
.btn.primary {
background: #222;
color: #fff;
border-color: #222;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.alert {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border-radius: 8px;
font-size: 14px;
}
.alert.error {
background: #ffe2e2;
color: #b30000;
}
.alert.success {
background: #e5ffe7;
color: #0f7a1f;
}
</style>