Yeni "Ham Kayıt Modu" ayarı (send_raw_segments_to_telegram, varsayılan
kapalı): açıksa her ham segment tamamlanır tamamlanmaz sinyal analizi/
STT/9:16 render zincirine hiç girmeden doğrudan Telegram'a gönderiliyor
ve sunucudan siliniyor. Kullanıcı transkript/9:16 istemediğinde ("sen
bana direk videoyu gönder") tüm analiz pipeline'ını atlayıp sadece
kayıt+gönder+sil yapan basit bir mod.
- RawSegmentStatus'a SENT_TO_TELEGRAM eklendi, RawSegment.filePath
nullable yapıldı (aynı ShortVideo.filePath deseni — gönderim sonrası
null, dosya diskten silinmiş demek).
- api-daemon/src/telegram.ts'e sendTelegramVideo eklendi (native fetch+
FormData+Blob ile multipart upload, ~50MB bot-limiti önceden kontrol
ediliyor) — worker/telegram.py'deki aynı fonksiyonun Node karşılığı.
- streamIngest.ts: segment tamamlanınca, mod açıksa signal-detection
kuyruğuna hiç eklemeden doğrudan gönderiyor (kuyruğa eklemek, aşağıda
dosyayı silmenin sinyal analizini bozmasına sebep olurdu).
- Panel: segment listelerinde filePath=null durumunda video/indir yerine
"Telegram'a gönderildi" notu (segments, channels/[id]).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Global SEGMENT_TIME_SEC varsayılanı 300->180 (docker-compose, .env.example,
env.ts, signal_detection.py fallback'i). 5 dakikalık segmentlerden çıkan
adaylar ~56MB'a kadar çıkabiliyordu — Telegram bot'ların sendVideo ile
yükleyebileceği ~50MB sınırını aşıp gönderim başarısız oluyordu (bkz.
delete_after_telegram_send). 3 dakika bu sınırın güvenli tarafında kalıyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Proxy IP'lerini havuza dağıttığımız aynı sorun cookie/hesap seviyesinde de
vardı: tek bir Google hesabı 7-8 kanalı taramaya çalışınca YouTube hesabın
kendisini şüpheli bulup çoğu/tüm kanalda "Sign in to confirm you're not a
bot" veriyordu — proxy IP'den bağımsız bir sinyal, IP dağıtımı bunu
çözemez.
Yeni CookieProfile tablosu: her kanal, DB id'sinden aynı deterministik hash
ile (proxyUrlForChannel'daki gibi) havuzdaki hesaplardan birine sabitlenir.
Her profil kendi ayrı geçici dosyasına yazılıyor (/tmp/yt-cookies-<id>.txt)
— tek paylaşılan dosya, farklı profil kullanan kanallar eşzamanlı
çalışınca birbirinin cookie'sini ezerdi. Hiç profil eklenmemişse eski tekli
ytdlp_cookies ayarına geri düşüyor (geriye dönük uyumlu, hemen kırılmaz).
Ayarlar sayfasında yeni "Cookie Hesapları" kartı: hesap ekle/sil/yenile,
her biri için ayrı bayatlık rozeti. Eski tekli cookie kartı "tekli/eski"
etiketiyle fallback olarak duruyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@liberotv'de segment indirirken sürekli "403 Forbidden / Failed to open
segment" hatası vardı — kendi ayrılmış proxy IP'sinde bile. yt-dlp'nin
GitHub issue'larında android client'ının videoplayback isteklerinde 403'e
sebep olduğu birden fazla kez raporlanmış. Tespit adımı (--simulate, format
çözümlemesi yok) risksiz olduğu için web,mweb,android'i koruyor; asıl
capture artık sadece web,mweb kullanıyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Dashboard'dan doğrulandı: isp.oxylabs.io:8001-8010 her biri farklı, statik
(rotasyon yapmayan) bir IP'ye karşılık geliyor. Ama PROXY_URL'in portu hep
sabitti — 7-8 kanalın tüm poll+capture trafiği tek bir IP'den (8001)
geçiyordu. Artık her kanal, DB id'sinden deterministik bir hash ile
havuzdaki 10 porttan birine sabitleniyor (aynı kanal hep aynı IP'yi
kullanıyor, farklı kanallar havuza yayılıyor) — hem YouTube'un tek IP
üzerindeki yük algısını azaltıyor hem de bant genişliğini paylaştırıyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Çöküp yeniden başlayan bir kanal (bkz. streamIngest'teki crash-loop notu)
dakikalar içinde art arda "başladı" + "hata" + "düzeldi" mesajı atıyordu —
birkaç kanal aynı anda flap edince gerçek bir spam'e dönüştü (kullanıcı
raporu: "sürekli bildirim geliyor"). Üç bildirim türü de artık kanal
başına ortak 10dk'lık bir soğuma süresini paylaşıyor — bir kanal ilk
olayını bildirir, sonra durum tekrar tekrar sıçrasa bile bir süre sessiz
kalır.
Not: bu sadece bildirim gürültüsünü kesiyor, alttaki crash-loop'un kendisi
(muhtemelen kaynak/proxy kapasitesi) hâlâ duruyor — o konu ayrı, kullanıcı
ile "önce test edelim" üzerinde anlaşıldı.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pollOnce her döngüde AKTİF kanalların hepsini (zaten kayıtta olanlar dahil)
yt-dlp'nin bot-check riskli /live sorgusuna sokuyordu — gereksiz. Birden
fazla kanal aynı anda canlıyken bu, aynı paylaşılan cookie+proxy üzerinden
gereksiz istek hacmi katlıyordu (muhtemelen 4-5 kanal eklenince birkaç
kanalın aynı anda tespit hatası vermeye başlamasının sebeplerinden biri).
Artık zaten açık bir StreamSession'ı olan kanal tamamen atlanıyor, kalan
kanallar arasına da 2sn gecikme eklendi (art arda patlama yerine seyrek).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Global SEGMENT_TIME_SEC varsayılanı 900->300 (docker-compose, .env.example,
env.ts, signal_detection.py fallback'i) — kanal bazlı override zaten
panelden ayarlanabiliyordu, bu sadece varsayılanı değiştiriyor.
Dashboard'daki kanal listesi tablo yerine kart-grid: her kanal kendi
kartında, durum rozetleri üstte belirgin, aksiyonlar altta — önceki
tablo düzeni (özellikle aksiyon sütunundaki sıkışık butonlar) okunması
zor bulunuyordu.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Poll hatası şu ana kadar sadece panele girip bakınca görülüyordu — proaktif
haberdar olma yolu yoktu. Şimdi bir kanal hata vermeye BAŞLADIĞINDA (her
60sn'de tekrar değil, sadece durum değişince) ve düzeldiğinde Telegram
bildirimi gidiyor. Ayarlar'dan kapatılabilir (notify_poll_error).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Channel.segmentTimeSec (nullable, null = global SEGMENT_TIME_SEC env
var'ı kullan) eklendi. Kanal detay sayfasından dakika cinsinden
girilebiliyor, StreamIngestJob'a taşınıp streamIngest.ts'te ffmpeg'in
-segment_time argümanına yansıyor. Sadece o kanal için bundan sonra
başlayacak yeni kayıt oturumlarını etkiler — halihazırda çalışan bir
ffmpeg process'inin segment süresi zaten sabitlenmiş durumda.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- lastPollErrors artık {message, at} tutuyor — panelde hangi hatanın ne
zaman oluştuğu görünmüyordu, eski/güncel ayrımı yapılamıyordu.
- "This live event will begin in N minutes" gibi zararsız mesajlar da
hata rozetine düşüyordu, "is not currently live" ile aynı gruba alındı.
- Yeni stt_enabled ayarı (Ayarlar sayfası): kapatılınca stt_scoring.py
OpenAI Whisper çağrısı yapmadan adayı PENDING_STT'de bırakıp çıkıyor —
konuşmasız/ambiyans yayınlarda (bkz. NASA testi) boşa API maliyeti
önleniyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Yayın durdurma, kanal duraklat/devam (tekli+toplu), şimdi kontrol et,
manuel URL ile zorla kayıt başlatma, canlı player+log önizleme, oturum
süre sayacı, başarısız/takılı render için tekrar dene ve iptal, poll
aralığı + ham segment TTL'yi panelden canlı değiştirme, cookie bayatlık
uyarısı, Telegram bildirim aç/kapa, ve kullanım/maliyet özeti (/stats).
api-daemon'ın yeni state-değiştiren endpoint'leri (stop/force-start/
render/cancel) INTERNAL_API_TOKEN ile korunuyor — bu daemon'ın portu
Coolify'de public'e açık olduğu için korumasız bırakmak güvenlik açığı
olurdu.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PRD'nin auto-purge lifecycle'ı: RawSegment analiz tamamlanmış
(PROCESSED/DISCARDED) ve hiçbir candidate'i PENDING_STT'de değilse
(yani hâlâ transkribe edilmeyi bekleyen yoksa) 24 saatten (yapılandırı-
labilir: RAW_SEGMENT_TTL_HOURS) eskiyse DB kaydı + dosyası siliniyor.
Saatlik kontrol ediliyor, api-daemon başlarken de bir kere çalışıyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
yt-dlp bunu da non-zero exit code ile bildiriyor, bu yüzden panelde
her kontrolde (kanal gerçekten sadece canlı değilken bile) kırmızı
"hata" rozeti görünüyordu — yanıltıcıydı. "is not currently live"
mesajı artık gerçek bir hata olarak işlenmiyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Regex tabanlı HTML çıkarımı IP'den bağımsız olarak tutarsız çıktı:
YouTube bazen player verisini (videoDetails) sunucu tarafında hiç
göndermiyor. yt-dlp'nin kendi extractor'ı engellenmediği sürece her
zaman doğru çözüyor, o yüzden kontrol tekrar yt-dlp'ye taşındı — bu
sefer üç katmanı birlikte kullanarak: cookie (oturum), pot-provider
(PO-token) ve PROXY_URL (temiz residential/ISP IP — Coolify
sunucusunun kendi IP'si bir günlük yoğun testten sonra işaretlendi).
Oxylabs ISP Proxies ile doğrulandı (gerçek ISP: CenturyLink), datacenter
proxy'lerin aksine.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
videoDetails yok ama sayfa consent duvarına da takılmıyor (büyük,
tam HTML dönüyor) — YouTube oynatıcı verisini bilerek vermiyor gibi
görünüyor. playabilityStatus.status/.reason genelde tam olarak
nedenini açıklıyor (LOGIN_REQUIRED, UNPLAYABLE, ERROR vb.).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cookie header'ı eklemek beklenen sonucu vermedi — @ertemsener kanalı
gerçekten canlıydı ama sistem hâlâ "değil" diyordu. Kör tahminle
devam etmek yerine, her kontrolde (sonuç ne olursa olsun) final URL,
HTML uzunluğu, cookie gönderilip gönderilmediği, videoDetails bulunup
bulunmadığı ve consent formu varlığı GET /status'ta lastPollDebug
alanında görünür kılındı.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Coolify sunucusu AB'de; oturumsuz istek YouTube'un interaktif consent
sayfasına düşüyor (o sayfada videoDetails yok), bu da gerçekten canlı
bir kanalı "değil" gibi gösteriyordu (doğrulandı: @ertemsener canlıydı,
sistem "değil" dedi). yt-dlp için zaten sakladığımız oturum açık
cookie'leri aynı fetch'e Cookie header'ı olarak eklendi — giriş yapılmış
hesaplar consent duvarını hiç görmüyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Proxy'ye gerek kalmadan bir çözüm: bugün boyunca düz HTTP fetch hiç
engellenmedi, sadece yt-dlp çağrıları bot-check/PO-token duvarına
takıldı. Önceki naive regex sorunuysa "videoId" alanının sayfada
onlarca kez (kanal video listesi, öneriler) geçmesiydi. Çözüm:
YouTube'un videoDetails objesi — sayfa gerçekten canlıysa tek ve
biricik olarak videoId/isLive/channelId'yi birlikte içeriyor (canlı
değilse obje hiç yok, doğrulandı). channelId çapraz kontrolüyle
birlikte hem doğru video hem yt-dlp'ye hiç dokunmadan (dolayısıyla
bot-check'e hiç maruz kalmadan) tespit yapılıyor. yt-dlp + cookie +
pot-provider artık sadece gerçek capture adımında kullanılıyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cookie'ler saatler içinde eskiyordu, her seferinde manuel Coolify env
güncellemesi + redeploy gerekiyordu. Artık DB'de (app_settings tablosu)
tutuluyor; /settings sayfasından yeni cookies.txt yapıştırılıp
kaydedilebiliyor, bir sonraki yt-dlp çağrısında redeploy gerekmeden
devreye giriyor. YTDLP_COOKIES_B64 env var mekanizması kaldırıldı.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bir redeploy, devam eden capture'ı temizlik kodu hiç çalışmadan
öldürüyordu — StreamSession.endedAt sonsuza dek null kalıyordu. Bu
sadece panelde yanlış "kayıtta" göstermekle kalmıyor, startSession()
açık bir session gördüğünde o kanal için yeni session başlatmayı
tamamen atlıyor — yani o kanal bir daha asla kayda alınamıyordu.
api-daemon başlarken artık tüm açık session'ları kapatıyor (taze bir
process başlangıcı = gerçekte devam eden capture yok demektir).
BullMQ lockDuration da 24 saatten 10 dakikaya indirildi — worker
canlıyken kilit otomatik yenileniyor, düşük değer sadece ölü bir
worker'ın job'ının ne kadar hızlı kurtarılacağını etkiliyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Kullanıcı panelindeki kayıtların kendi yayınıyla alakasız olduğunu
bildirdi. Sebep: channel /live sayfası onlarca alakasız videoId
içeriyor (kanal video listesi, öneriler); düz regex sayfadaki İLK
videoId'yi alıyordu, bu da rastgele başka bir videoydu — isLive:true
kontrolü de aynı bağlamda değildi. yt-dlp'nin extractor'ı kanalın asıl
o anki canlı videosunu doğru çözüyor; -f (format) verilmediği için
PO-token duvarını da tetiklemiyor (format çözümleme sadece gerçek
capture adımında, -f ile, gerekiyor).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- streamIngest worker concurrency 3'ten sabit değildi, artık
MAX_CONCURRENT_CAPTURES (varsayılan 10) ile ayarlanabiliyor. Eskiden
3'ten fazla kanal aynı anda canlıya geçerse fazlası, ilk 3'ten biri
bitene kadar (saatlerce) hiç kayda alınmıyordu.
- Python worker'larda (signal-detection, stt-scoring) bullmq varsayılan
concurrency'si 1'di — WORKER_CONCURRENCY (varsayılan 5) ile
paralelleştirildi, birden fazla kanal aynı anda segment/transkript
üretirse sıraya takılmasın diye.
- Frontend artık shared-media volume'üne bağlı: /api/segments/[id]/video
route'u Range destekli video stream/indirme sağlıyor. Segment ve
kanal detay sayfalarına <video> oynatıcı, indirme linki ve onaylı
silme (RawSegment + CandidateSegment, dosya dahil) eklendi.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Gerçek capture denemesinde debug endpoint'i asıl nedeni gösterdi:
"n challenge solving failed" — YouTube'un imza çözme JS challenge'ı
için yt-dlp'nin bir JS runtime'ı yoktu, bu da formatların eksik
kalıp "page needs to be reloaded" ile sonuçlanmasına yol açıyordu.
node:22-slim zaten Node 22 içeriyor (minimum gereksinim); yt-dlp'yi
"yt-dlp[default]" (EJS solver dahil) olarak kurup capture çağrısına
--js-runtimes node eklendi. Lokal olarak doğrulandı: aynı video artık
formatları hatasız çözüyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
İlk gerçek capture denemesi 2 saniyede sessionı bitirdi (0 segment) —
Coolify /logs endpoint'i api-daemon'un stdout'unu güvenilir şekilde
izole edemediği için sebebi göremedik. Artık her session'ın son
yt-dlp/ffmpeg stderr satırları bellekte tutulup HTTP'den okunabiliyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PO-token duvarı polling'de hiç gerekli değildi — sadece stream byte'ı
indirirken (capture) lazım. yt-dlp --simulate bile format çözümlemeye
çalıştığı için PO-token'a takılıyordu. Artık /channel/<id>/live
sayfasını düz fetch ile çekip ytInitialData içindeki isLive/videoId'yi
regex ile okuyoruz — bot-check/cookie/PO-token'a hiç maruz kalmıyor.
yt-dlp + cookie + pot-provider sadece gerçek capture adımında kalıyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cookie bot-check'i çözdü ama "The page needs to be reloaded" hatası
kalıcıydı — YouTube artık proof-of-origin token da istiyor. Coolify
stack'ine brainicism/bgutil-ytdlp-pot-provider sidecar servisi
(sc_pot_provider, port 4416) eklendi, api-daemon imajına Python
plugin'i kuruldu, her yt-dlp çağrısına
--extractor-args youtubepot-bgutilhttp:base_url=... eklendi.
ytdlpCookieArgs -> ytdlpAntiBotArgs olarak birleştirildi.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cookie tek başına bot-check'i çözüyordu ama android client ile
birlikte kullanılınca "No video formats found" hatası sürekli
tekrarladı — android client + cookie kombinasyonu tutarsız bir
manifest döndürüyor gibi. android client'sız, sadece cookie +
varsayılan web client + bestvideo+bestaudio/best format seçiciyle
aynı video id lokal olarak sorunsuz çözüldü.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cookie bot-check'i çözdü ama yeni bir hata çıktı: "No video formats
found" / "Requested format is not available". AyrisTech'in canlı
yayını dikey (1080x1920) ve HLS formatları video-only/audio-only
ayrı geliyor — tek parça "best" format hiç yok. Hem live-check hem
capture çağrısını "bestvideo+bestaudio/best" seçicisine çevirdik;
lokal olarak doğrulandı (yt-dlp video id'yi doğru döndürüyor).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
android player_client YouTube'un "Sign in to confirm you're not a bot"
duvarını aşmadı — Coolify sunucusunun IP'si datacenter olarak
işaretlenmiş. Kimlik doğrulanmış bir tarayıcı oturumunun cookies.txt'i
YTDLP_COOKIES_B64 env değişkeni (Coolify'da ayarlandı, repo'da yok)
üzerinden container'a taşınıp /tmp'e yazılıyor ve her yt-dlp
çağrısına --cookies olarak veriliyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AyrisTech kanalı gerçekten canlıydı ama Coolify sunucusunun IP'si
YouTube'un "Sign in to confirm you're not a bot" duvarına takıldı
(bu ortamdan aynı komut sorunsuz çalıştı — IP itibarına bağlı).
Hem live-check hem capture çağrısına --extractor-args
"youtube:player_client=android" eklendi; bu client genelde aynı
PO-token doğrulamasına tabi değil.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
yt-dlp hem "kanal canlı değil" hem de gerçek bir hata (ağ engeli,
bot tespiti, extractor kırılması) durumunda non-zero exit code
veriyor; ikisi ayırt edilemiyordu. Son hata mesajı artık kanal
başına saklanıp GET /status içinde lastPollError alanında dönüyor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Postgres canlıydı ama şema hiç uygulanmamıştı (migrations/ klasörü boştu).
Bu ortamdan Coolify'ın Postgres'ine ağ erişimi yok (5432 filtreli), bu yüzden
`prisma migrate dev` yerine offline schema diff (`migrate diff --from-empty`)
ile ilk migration SQL'i üretildi. api-daemon container'ı artık başlarken
`prisma migrate deploy` çalıştırıyor. Tüm servislere restart: unless-stopped
eklendi ki Postgres henüz hazır değilken başlayan container kendini toparlasın.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
yt-dlp headless capture (15dk segmentleme), ses peak + chat velocity sinyal
tespiti, OpenAI Whisper STT (kelime zaman damgalı), BullMQ/Redis/Postgres
altyapısı ve kanal durumu + transkript kütüphanesi gösteren Next.js panel.
LLM virality skorlama, render/crop/altyazı ve multi-platform dağıtım bu
fazın kapsamı dışında.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>