api issues

This commit is contained in:
salvacybersec
2025-11-13 08:51:55 +03:00
parent 86ce4913b8
commit bff8164c5d
26 changed files with 15284 additions and 57 deletions

152
API.md
View File

@@ -16,18 +16,28 @@ Production için base URL değişebilir.
API key'i iki şekilde gönderebilirsiniz:
### 1. HTTP Header (Önerilen)
### 1. HTTP Header (Programatik kullanım için önerilen)
```http
X-API-Key: your_api_key_here
```
### 2. Query Parameter
**Not:** RSS okuyucular HTTP header gönderemediği için bu yöntem sadece programatik kullanım için uygundur.
### 2. Query Parameter (RSS okuyucular ve tarayıcılar için zorunlu)
```
?api_key=your_api_key_here
```
**Önemli:** RSS okuyucular, tarayıcılar ve feed aggregator'lar için **mutlaka query parameter** kullanılmalıdır çünkü bu uygulamalar HTTP header gönderemez.
**Güvenlik Notu:** API key'i URL'de kullanmak güvenlik açısından ideal değildir çünkü:
- URL'ler log dosyalarında, tarayıcı geçmişinde ve referrer header'larında görünebilir
- Ancak RSS okuyucular için bu tek seçenektir
- Production'da farklı API key'ler kullanarak riski azaltabilirsiniz
- API key'lerinizi düzenli olarak rotate edin
### API Key Alma
API key'ler `config/security.yaml` dosyasından yönetilir. Yeni bir API key eklemek için:
@@ -93,21 +103,46 @@ YouTube kanalı için transcript feed'i oluşturur.
**Örnek İstekler:**
**URL-Based Sorgular (RSS Okuyucular ve Tarayıcılar için):**
```bash
# Channel ID ile
# Channel ID ile (API key URL'de)
http://localhost:5000/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&api_key=demo_key_12345&format=Atom
# Channel handle ile (API key URL'de)
http://localhost:5000/?channel=@tavakfi&api_key=demo_key_12345&format=Atom&max_items=10
# Channel URL ile (API key URL'de) - ÖNERİLEN
http://localhost:5000/?channel_url=https://www.youtube.com/@tavakfi&api_key=demo_key_12345&format=Atom&max_items=50
# RSS format ile
http://localhost:5000/?channel_url=https://www.youtube.com/@politicalfronts&api_key=demo_key_12345&format=Rss&max_items=20
```
**Programatik Kullanım (HTTP Header ile):**
```bash
# Channel ID ile (Header'da API key)
curl -H "X-API-Key: demo_key_12345" \
"http://localhost:5000/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&format=Atom"
# Channel handle ile
# Channel handle ile (Header'da API key)
curl -H "X-API-Key: demo_key_12345" \
"http://localhost:5000/?channel=@tavakfi&format=Atom"
# Channel URL ile
# Channel URL ile (Header'da API key)
curl -H "X-API-Key: demo_key_12345" \
"http://localhost:5000/?channel_url=https://www.youtube.com/@tavakfi&format=Atom&max_items=50"
```
# Query parametresi ile API key
curl "http://localhost:5000/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&api_key=demo_key_12345&format=Rss"
**Production URL Örnekleri:**
```
# Production URL ile Channel URL kullanımı
https://yt2feed.aligundogar.com.tr/?channel_url=https://www.youtube.com/@politicalfronts&api_key=your_api_key&format=Atom
# Production URL ile Channel ID kullanımı
https://yt2feed.aligundogar.com.tr/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&api_key=your_api_key&format=Rss&max_items=50
```
**Başarılı Yanıt:**
@@ -332,10 +367,60 @@ API CORS desteği sağlar. Preflight request'ler için `OPTIONS` metodu kullanı
RSS reader uygulamanızda feed URL'si olarak kullanın:
**Önemli:** RSS okuyucular HTTP header gönderemediği için API key'i **mutlaka query parametresi** olarak eklemeniz gerekir.
**URL Formatı:**
```
http://your-api.com/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&format=Rss&api_key=your_api_key
BASE_URL/?channel_url=YOUTUBE_CHANNEL_URL&api_key=YOUR_API_KEY&format=Atom&max_items=10
```
**Örnek URL'ler (Production):**
```
# Channel URL ile (ÖNERİLEN - En kolay ve güvenilir)
https://yt2feed.aligundogar.com.tr/?channel_url=https://www.youtube.com/@politicalfronts&api_key=your_api_key&format=Atom
# Channel URL ile RSS format
https://yt2feed.aligundogar.com.tr/?channel_url=https://www.youtube.com/@politicalfronts&api_key=your_api_key&format=Rss&max_items=50
# Channel ID ile
https://yt2feed.aligundogar.com.tr/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&api_key=your_api_key&format=Atom&max_items=20
# Channel Handle ile
https://yt2feed.aligundogar.com.tr/?channel=@tavakfi&api_key=your_api_key&format=Atom&max_items=50
# Channel URL ile (Channel ID formatında)
https://yt2feed.aligundogar.com.tr/?channel_url=https://www.youtube.com/channel/UC9h8BDcXwkhZtnqoQJ7PggA&api_key=your_api_key&format=Rss
```
**Örnek URL'ler (Localhost - Test için):**
```
# Localhost test URL'i
http://localhost:5000/?channel_url=https://www.youtube.com/@politicalfronts&api_key=demo_key_12345&format=Atom
# Localhost ile Channel ID
http://localhost:5000/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&api_key=demo_key_12345&format=Rss&max_items=10
```
**RSS Okuyucu Adımları:**
1. RSS okuyucunuzda "Feed Ekle", "Subscribe" veya "Add Feed" seçeneğini açın
2. Feed URL alanına yukarıdaki formatlardan birini yapıştırın
3. `your_api_key` kısmını kendi API key'inizle değiştirin
4. Feed'i ekleyin ve kaydedin
**Popüler RSS Okuyucular:**
- **Feedly**: Feed URL'sini direkt yapıştırın
- **Inoreader**: "Add New" > "Feed" > URL yapıştırın
- **NewsBlur**: "Add Site" > URL yapıştırın
- **The Old Reader**: "Add Subscription" > URL yapıştırın
- **NetNewsWire**: "File" > "Add Feed" > URL yapıştırın
**Not:**
- İlk istekte transcript'ler henüz işlenmemiş olabilir. Birkaç dakika bekleyip tekrar deneyin.
- API key'inizi URL'de paylaşmayın, sadece kendi RSS okuyucunuzda kullanın.
### 2. Programatik Kullanım (Python)
```python
@@ -363,6 +448,31 @@ else:
### 3. Programatik Kullanım (JavaScript)
**URL-Based (Tarayıcı için):**
```javascript
// Tarayıcıdan kullanım - API key URL'de
const apiKey = "your_api_key_here";
const channelUrl = "https://www.youtube.com/@politicalfronts";
const feedUrl = `https://yt2feed.aligundogar.com.tr/?channel_url=${encodeURIComponent(channelUrl)}&api_key=${apiKey}&format=Atom`;
fetch(feedUrl)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.text();
})
.then(feedContent => {
console.log(feedContent);
})
.catch(error => {
console.error("Error:", error);
});
```
**Header-Based (Node.js/Backend için):**
```javascript
const apiKey = "your_api_key_here";
const channelId = "UC9h8BDcXwkhZtnqoQJ7PggA";
@@ -388,13 +498,37 @@ fetch(`http://localhost:5000/?channel_id=${channelId}&format=Atom`, {
### 4. cURL ile Test
**URL-Based Sorgular (RSS Okuyucular ve Tarayıcılar için):**
```bash
# API key ile test
# API key URL'de - Channel URL ile (ÖNERİLEN)
API_KEY="demo_key_12345"
CHANNEL_URL="https://www.youtube.com/@politicalfronts"
curl "http://localhost:5000/?channel_url=${CHANNEL_URL}&api_key=${API_KEY}&format=Atom&max_items=50"
# API key URL'de - Channel ID ile
CHANNEL_ID="UC9h8BDcXwkhZtnqoQJ7PggA"
curl "http://localhost:5000/?channel_id=${CHANNEL_ID}&api_key=${API_KEY}&format=Rss&max_items=20"
# Production URL ile
curl "https://yt2feed.aligundogar.com.tr/?channel_url=https://www.youtube.com/@politicalfronts&api_key=${API_KEY}&format=Atom"
```
**Header-Based Sorgular (Programatik Kullanım için):**
```bash
# API key header'da - Channel ID ile
API_KEY="demo_key_12345"
CHANNEL_ID="UC9h8BDcXwkhZtnqoQJ7PggA"
curl -H "X-API-Key: $API_KEY" \
"http://localhost:5000/?channel_id=$CHANNEL_ID&format=Atom&max_items=50"
# API key header'da - Channel URL ile
CHANNEL_URL="https://www.youtube.com/@tavakfi"
curl -H "X-API-Key: $API_KEY" \
"http://localhost:5000/?channel_url=${CHANNEL_URL}&format=Atom&max_items=50"
```
## Notlar

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -116,13 +116,21 @@ class SecurityManager:
Returns:
Geçerli mi?
YouTube Channel ID formatı:
- UC ile başlamalı
- Toplam 24 karakter (UC + 22 karakter)
- Sadece alfanumerik karakterler ve alt çizgi (_) içermeli
- Tire (-) karakteri içermemeli
"""
if not channel_id:
return False
# UC ile başlayan 24 karakter
if re.match(r'^UC[a-zA-Z0-9_-]{22}$', channel_id):
return True
# UC ile başlayan, 24 karakter, sadece alfanumerik ve alt çizgi (tire YOK)
if re.match(r'^UC[a-zA-Z0-9_]{22}$', channel_id):
# Double check: Tire karakteri içermemeli
if '-' not in channel_id:
return True
return False
@@ -212,7 +220,15 @@ def require_api_key(f):
security = get_security_manager()
# API key'i header'dan veya query'den al
api_key = request.headers.get('X-API-Key') or request.args.get('api_key')
api_key_header = request.headers.get('X-API-Key')
api_key_query = request.args.get('api_key')
api_key = api_key_header or api_key_query
# Debug logging (sadece query parametresi varsa)
if api_key_query and not api_key_header:
import logging
logger = logging.getLogger(__name__)
logger.debug(f"[API_KEY] Query parametresinden API key alındı: {api_key_query[:10]}... (uzunluk: {len(api_key_query) if api_key_query else 0})")
is_valid, key_info = security.validate_api_key(api_key)

View File

@@ -4,35 +4,59 @@ RSS-Bridge kullanarak video metadata çıkarımı
import feedparser
import re
import requests
import logging
from urllib.parse import urlencode
from typing import List, Dict, Optional
from datetime import datetime
logger = logging.getLogger(__name__)
def get_channel_id_from_handle(handle_url: str) -> Optional[str]:
"""
Channel handle URL'inden Channel ID'yi web scraping ile bulur.
Örnek: https://www.youtube.com/@tavakfi -> UC...
YouTube Channel ID formatı: UC ile başlayan, 24 karakter, sadece alfanumerik ve alt çizgi
"""
try:
response = requests.get(handle_url)
logger.info(f"[CHANNEL_ID] Channel ID çıkarılıyor: {handle_url}")
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
response = requests.get(handle_url, headers=headers, timeout=10)
response.raise_for_status()
html_content = response.text
# İlk pattern: "externalId":"UC..."
match = re.search(r'"externalId":"(UC[a-zA-Z0-9_-]{22})"', html_content)
if match:
return match.group(1)
# YouTube Channel ID pattern: UC ile başlayan, 24 karakter, sadece alfanumerik ve alt çizgi (tire YOK)
# Pattern'ler: UC + 22 karakter = toplam 24 karakter
patterns = [
(r'"externalId":"(UC[a-zA-Z0-9_]{22})"', 'externalId'),
(r'"channelId":"(UC[a-zA-Z0-9_]{22})"', 'channelId'),
(r'"browseEndpoint"\s*:\s*\{[^}]*"browseId"\s*:\s*"(UC[a-zA-Z0-9_]{22})"', 'browseEndpoint'),
(r'channelId["\']?\s*:\s*["\']?(UC[a-zA-Z0-9_]{22})', 'genel channelId'),
(r'/channel/(UC[a-zA-Z0-9_]{22})', 'URL pattern'),
]
# Alternatif pattern: "channelId":"UC..."
match_alt = re.search(r'"channelId":"(UC[a-zA-Z0-9_-]{22})"', html_content)
if match_alt:
return match_alt.group(1)
for pattern, pattern_name in patterns:
match = re.search(pattern, html_content)
if match:
channel_id = match.group(1)
# Double check: UC ile başlamalı ve 24 karakter olmalı
if channel_id.startswith('UC') and len(channel_id) == 24:
# Tire karakteri içermemeli
if '-' not in channel_id:
logger.info(f"[CHANNEL_ID] ✅ Channel ID bulundu: {channel_id} (pattern: {pattern_name})")
return channel_id
else:
logger.warning(f"[CHANNEL_ID] ⚠️ Geçersiz channel ID (tire içeriyor): {channel_id}")
logger.warning(f"[CHANNEL_ID] ❌ Channel ID bulunamadı: {handle_url}")
return None
except requests.exceptions.RequestException as e:
logger.error(f"[CHANNEL_ID] ❌ Hata: {type(e).__name__} - {str(e)}")
raise Exception(f"Error fetching channel page: {e}")

View File

@@ -3,6 +3,7 @@ Flask web server - RSS-Bridge benzeri URL template sistemi
"""
from flask import Flask, request, Response, jsonify, g
from typing import Optional
from urllib.parse import unquote, urlparse
import sys
import os
import yaml
@@ -58,8 +59,22 @@ def add_security_headers(response):
config = load_security_config()
headers = config.get('security_headers', {})
# RSS feed'ler için Content-Security-Policy'yi daha esnek yap
# RSS okuyucular ve tarayıcılar için sorun çıkarmasın
is_feed_response = (
'application/atom+xml' in response.content_type or
'application/rss+xml' in response.content_type or
'application/xml' in response.content_type or
'text/xml' in response.content_type
)
for header, value in headers.items():
response.headers[header] = value
# RSS feed'ler için CSP'yi atla veya daha esnek yap
if header == 'Content-Security-Policy' and is_feed_response:
# RSS feed'ler için CSP'yi daha esnek yap
response.headers[header] = "default-src 'self' 'unsafe-inline' data: blob: *"
else:
response.headers[header] = value
# CORS headers
cors_config = config.get('cors', {})
@@ -90,7 +105,29 @@ def add_security_headers(response):
@app.route('/<path:path>', methods=['OPTIONS'])
def handle_options(path=None):
"""CORS preflight request handler"""
return Response(status=200)
config = load_security_config()
cors_config = config.get('cors', {})
response = Response(status=200)
if cors_config.get('enabled', True):
origins = cors_config.get('allowed_origins', ['*'])
if '*' in origins:
response.headers['Access-Control-Allow-Origin'] = '*'
else:
origin = request.headers.get('Origin')
if origin in origins:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Methods'] = ', '.join(
cors_config.get('allowed_methods', ['GET', 'OPTIONS'])
)
response.headers['Access-Control-Allow-Headers'] = ', '.join(
cors_config.get('allowed_headers', ['Content-Type', 'X-API-Key'])
)
response.headers['Access-Control-Max-Age'] = '3600'
return response
# Uygulama başlangıcında security'yi initialize et
init_app_security()
@@ -130,7 +167,7 @@ def normalize_channel_id(channel_id: Optional[str] = None,
channel: Optional[str] = None,
channel_url: Optional[str] = None) -> Optional[str]:
"""
Farklı formatlardan channel ID'yi normalize et
Farklı formatlardan channel ID'yi normalize et ve validate et
Args:
channel_id: Direkt Channel ID (UC...)
@@ -138,36 +175,74 @@ def normalize_channel_id(channel_id: Optional[str] = None,
channel_url: Full YouTube channel URL
Returns:
Normalize edilmiş Channel ID veya None
Normalize edilmiş ve validate edilmiş Channel ID veya None
"""
security = get_security_manager()
normalized_id = None
# Direkt Channel ID varsa
if channel_id:
if channel_id.startswith('UC') and len(channel_id) == 24:
return channel_id
normalized_id = channel_id
# Eğer URL formatında ise parse et
if 'youtube.com/channel/' in channel_id:
elif 'youtube.com/channel/' in channel_id:
parts = channel_id.split('/channel/')
if len(parts) > 1:
return parts[-1].split('?')[0].split('/')[0]
normalized_id = parts[-1].split('?')[0].split('/')[0]
# Channel handle (@username)
if channel:
if not normalized_id and channel:
# Channel parametresini normalize et (@ işareti olabilir veya olmayabilir)
channel = channel.strip()
if not channel.startswith('@'):
channel = f"@{channel}"
handle_url = f"https://www.youtube.com/{channel}"
return get_channel_id_from_handle(handle_url)
logger.info(f"[NORMALIZE] Channel handle URL oluşturuldu: {handle_url}")
normalized_id = get_channel_id_from_handle(handle_url)
# Channel URL
if channel_url:
# Handle URL
if not normalized_id and channel_url:
# URL'yi temizle ve normalize et
channel_url = channel_url.strip()
# Handle URL (@username formatı)
if '/@' in channel_url:
return get_channel_id_from_handle(channel_url)
# URL'den handle'ı çıkar
if '/@' in channel_url:
# https://www.youtube.com/@username formatı
normalized_id = get_channel_id_from_handle(channel_url)
else:
# Sadece @username formatı
handle = channel_url.replace('@', '').strip()
if handle:
handle_url = f"https://www.youtube.com/@{handle}"
normalized_id = get_channel_id_from_handle(handle_url)
# Channel ID URL
elif '/channel/' in channel_url:
parts = channel_url.split('/channel/')
if len(parts) > 1:
return parts[-1].split('?')[0].split('/')[0]
channel_id_part = parts[-1].split('?')[0].split('/')[0].split('&')[0]
# Eğer UC ile başlıyorsa ve 24 karakter ise, direkt kullan
if channel_id_part.startswith('UC') and len(channel_id_part) == 24:
normalized_id = channel_id_part
else:
# Parse etmeye çalış
normalized_id = channel_id_part
# Sadece handle (@username) formatı
elif channel_url.startswith('@'):
handle = channel_url.replace('@', '').strip()
if handle:
handle_url = f"https://www.youtube.com/@{handle}"
normalized_id = get_channel_id_from_handle(handle_url)
# Direkt channel ID formatı (UC...)
elif channel_url.startswith('UC') and len(channel_url) == 24:
normalized_id = channel_url
# Validate: Channel ID formatını kontrol et
if normalized_id and security.validate_channel_id(normalized_id):
return normalized_id
# Geçersiz format
return None
@@ -182,6 +257,28 @@ def process_channel(channel_id: str, max_items: int = 50) -> dict:
extractor = get_extractor()
cleaner = get_cleaner()
# ÖNCE: Mevcut işlenmiş videoları kontrol et
existing_processed = db.get_processed_videos(limit=max_items, channel_id=channel_id)
logger.info(f"[PROCESS] Channel {channel_id} için {len(existing_processed)} mevcut işlenmiş video bulundu")
# Eğer yeterli sayıda işlenmiş video varsa, onları hemen döndür
if len(existing_processed) >= max_items:
logger.info(f"[PROCESS] ✅ Yeterli işlenmiş video var ({len(existing_processed)}), yeni işleme başlatılmıyor")
return {
'videos': existing_processed[:max_items],
'channel_id': channel_id,
'count': len(existing_processed[:max_items])
}
# Eğer mevcut işlenmiş videolar varsa ama yeterli değilse, onları döndür ve yeni işlemeleri başlat
# Ancak sadece ilk batch'i işle (hızlı yanıt için)
if len(existing_processed) > 0:
logger.info(f"[PROCESS] ⚠️ Mevcut işlenmiş video var ama yeterli değil ({len(existing_processed)}/{max_items}), yeni işleme başlatılıyor")
# Mevcut videoları döndürmek için sakla
videos_to_return = existing_processed.copy()
else:
videos_to_return = []
# RSS-Bridge'den videoları çek (max_items'ın 2 katı kadar çek, böylece yeterli video olur)
# RSS-Bridge'den daha fazla video çekiyoruz çünkü bazıları transcript'siz olabilir
rss_bridge_limit = max(max_items * 2, 50) # En az 50 video çek
@@ -222,13 +319,24 @@ def process_channel(channel_id: str, max_items: int = 50) -> dict:
all_pending_videos = [v for v in db.get_pending_videos() if v['channel_id'] == channel_id]
logger.info(f"[PROCESS] Channel {channel_id} için {len(all_pending_videos)} bekleyen video bulundu (max_items: {max_items})")
# Eğer mevcut işlenmiş videolar varsa, sadece eksik kadar işle
remaining_needed = max_items - len(videos_to_return)
# max_items kadar transcript işlenene kadar batch'ler halinde işle
total_batches = (len(all_pending_videos) + batch_size - 1) // batch_size
current_batch = 0
# İlk istek için sadece ilk batch'i işle (hızlı yanıt için)
# Sonraki isteklerde daha fazla işlenmiş video olacak
max_batches_to_process = 1 if len(videos_to_return) == 0 else min(3, total_batches) # İlk istekte 1 batch, sonra 3 batch
for batch_start in range(0, len(all_pending_videos), batch_size):
if processed_count >= max_items:
logger.info(f"[PROCESS] Maksimum transcript sayısına ulaşıldı ({processed_count}/{max_items})")
if processed_count >= remaining_needed:
logger.info(f"[PROCESS] Yeterli transcript işlendi ({processed_count}/{remaining_needed})")
break
if current_batch >= max_batches_to_process:
logger.info(f"[PROCESS] İlk batch'ler işlendi ({current_batch}/{max_batches_to_process}), kalan işlemeler sonraki isteklerde yapılacak")
break
current_batch += 1
@@ -292,24 +400,44 @@ def process_channel(channel_id: str, max_items: int = 50) -> dict:
logger.info(f"[BATCH] Batch {current_batch}/{total_batches} tamamlandı - İşlenen: {batch_processed}, Cache: {batch_cached}, Başarısız: {batch_failed}")
# Batch tamamlandı, uzun bekleme (YouTube IP blocking önleme için)
if processed_count < max_items and batch_start + batch_size < len(all_pending_videos):
# Blocking varsa daha uzun bekle
wait_time = 60 + random.uniform(0, 30) # 60-90 saniye random (human-like)
logger.info(f"[BATCH] Batch'ler arası bekleme: {wait_time:.1f} saniye ({wait_time/60:.1f} dakika) - YouTube IP blocking önleme")
# İlk batch'ler için daha kısa bekleme (hızlı yanıt için), sonraki batch'ler için uzun bekleme
if processed_count < remaining_needed and batch_start + batch_size < len(all_pending_videos):
# İlk batch'ler için kısa bekleme (2-5 saniye), sonraki batch'ler için uzun bekleme (60-90 saniye)
if current_batch <= max_batches_to_process:
wait_time = 2 + random.uniform(0, 3) # 2-5 saniye (hızlı yanıt için)
logger.info(f"[BATCH] Batch'ler arası kısa bekleme: {wait_time:.1f} saniye (hızlı yanıt için)")
else:
wait_time = 60 + random.uniform(0, 30) # 60-90 saniye random (human-like)
logger.info(f"[BATCH] Batch'ler arası uzun bekleme: {wait_time:.1f} saniye ({wait_time/60:.1f} dakika) - YouTube IP blocking önleme")
time.sleep(wait_time)
# İşlenmiş videoları getir
processed_videos = db.get_processed_videos(
# İşlenmiş videoları getir (yeni işlenenler)
newly_processed = db.get_processed_videos(
limit=max_items,
channel_id=channel_id
)
logger.info(f"[PROCESS] ✅ Channel {channel_id} işleme tamamlandı - {len(processed_videos)} işlenmiş video döndürülüyor")
# Mevcut videoları ve yeni işlenen videoları birleştir (duplicate kontrolü ile)
all_processed_videos = videos_to_return.copy() # Önce mevcut videoları ekle
existing_ids = {v['video_id'] for v in all_processed_videos}
# Yeni işlenen videoları ekle
for video in newly_processed:
if video['video_id'] not in existing_ids and len(all_processed_videos) < max_items:
all_processed_videos.append(video)
# Tarihe göre sırala (en yeni önce)
all_processed_videos.sort(
key=lambda x: x.get('published_at_utc', '') or '',
reverse=True
)
logger.info(f"[PROCESS] ✅ Channel {channel_id} işleme tamamlandı - {len(all_processed_videos)} işlenmiş video döndürülüyor (Mevcut: {len(videos_to_return)}, Yeni işlenen: {len(newly_processed)})")
return {
'videos': processed_videos,
'videos': all_processed_videos[:max_items],
'channel_id': channel_id,
'count': len(processed_videos)
'count': len(all_processed_videos[:max_items])
}
@@ -325,10 +453,58 @@ def generate_feed():
- /?channel=@tavakfi&format=Atom
- /?channel_url=https://www.youtube.com/@tavakfi&format=Atom
"""
# User-Agent kontrolü (RSS okuyucu tespiti için)
user_agent = request.headers.get('User-Agent', '')
is_rss_reader = any(keyword in user_agent.lower() for keyword in [
'rss', 'feed', 'reader', 'aggregator', 'feedly', 'newsblur',
'inoreader', 'theoldreader', 'netnewswire', 'reeder'
])
# Query parametrelerini al (validate_input decorator zaten sanitize etti)
channel_id = request.args.get('channel_id')
channel = request.args.get('channel') # @username veya username
channel_url = request.args.get('channel_url')
# URL decode işlemi (tarayıcılar URL'leri encode edebilir, özellikle channel_url içinde başka URL varsa)
channel_id_raw = request.args.get('channel_id')
channel_raw = request.args.get('channel') # @username veya username
channel_url_raw = request.args.get('channel_url')
# Channel ID'yi decode et
channel_id = None
if channel_id_raw:
channel_id = unquote(channel_id_raw) if '%' in channel_id_raw else channel_id_raw
# Channel handle'ı decode et
channel = None
if channel_raw:
channel = unquote(channel_raw) if '%' in channel_raw else channel_raw
# @ işaretini temizle ve normalize et
channel = channel.strip().lstrip('@')
# Channel URL'yi decode et (eğer encode edilmişse)
# Flask request.args zaten decode eder ama channel_url içinde başka URL olduğu için double encoding olabilir
channel_url = None
if channel_url_raw:
# Önce raw değeri al (Flask'ın decode ettiği değer)
channel_url = channel_url_raw
# Eğer hala encode edilmiş görünüyorsa (%, + gibi karakterler varsa), decode et
if '%' in channel_url or '+' in channel_url:
# Birden fazla kez encode edilmiş olabilir, güvenli decode
max_decode_attempts = 3
for _ in range(max_decode_attempts):
decoded = unquote(channel_url)
if decoded == channel_url: # Artık decode edilecek bir şey yok
break
channel_url = decoded
if '%' not in channel_url: # Tamamen decode edildi
break
# URL formatını kontrol et ve düzelt
if channel_url and not channel_url.startswith(('http://', 'https://')):
# Eğer protocol yoksa, https ekle
if channel_url.startswith('www.youtube.com') or channel_url.startswith('youtube.com'):
channel_url = 'https://' + channel_url
elif channel_url.startswith('@'):
channel_url = 'https://www.youtube.com/' + channel_url
format_type = request.args.get('format', 'Atom').lower() # Atom veya Rss
try:
max_items = int(request.args.get('max_items', 10)) # Default: 10 transcript
@@ -337,18 +513,74 @@ def generate_feed():
except (ValueError, TypeError):
max_items = 10
# Debug logging (tarayıcı istekleri için)
logger.info(f"[REQUEST] Tarayıcı isteği - Raw params: channel_id={channel_id_raw}, channel={channel_raw}, channel_url={channel_url_raw[:100] if channel_url_raw else None}")
logger.info(f"[REQUEST] Processed params: channel_id={channel_id}, channel={channel}, channel_url={channel_url[:100] if channel_url else None}")
logger.info(f"[REQUEST] Full URL: {request.url}")
logger.info(f"[REQUEST] Query string: {request.query_string.decode('utf-8') if request.query_string else None}")
# RSS okuyucu tespiti için log
if is_rss_reader:
logger.info(f"[RSS_READER] RSS okuyucu tespit edildi: {user_agent[:100]}")
# Channel ID'yi normalize et
normalized_channel_id = normalize_channel_id(
channel_id=channel_id,
channel=channel,
channel_url=channel_url
)
try:
normalized_channel_id = normalize_channel_id(
channel_id=channel_id,
channel=channel,
channel_url=channel_url
)
logger.info(f"[REQUEST] Normalized channel_id: {normalized_channel_id}")
except Exception as e:
logger.error(f"[REQUEST] ❌ Channel ID normalize hatası: {type(e).__name__} - {str(e)}")
normalized_channel_id = None
if not normalized_channel_id:
return jsonify({
'error': 'Channel ID bulunamadı',
error_msg = 'Channel ID bulunamadı veya geçersiz format'
if channel_url:
error_msg += f'. URL: {channel_url}'
elif channel:
error_msg += f'. Handle: {channel}'
elif channel_id:
error_msg += f'. Channel ID: {channel_id}'
logger.warning(f"[REQUEST] ❌ Channel ID bulunamadı - Raw: channel_id={channel_id_raw}, channel={channel_raw}, channel_url={channel_url_raw}")
logger.warning(f"[REQUEST] ❌ Processed: channel_id={channel_id}, channel={channel}, channel_url={channel_url}")
# RSS okuyucular için daha açıklayıcı hata mesajı
if is_rss_reader:
logger.warning(f"[RSS_READER] Channel ID bulunamadı - URL: {channel_url or channel or channel_id}")
return jsonify({
'error': error_msg,
'message': 'RSS okuyucunuzdan feed eklerken lütfen geçerli bir YouTube kanal URL\'si kullanın',
'received_params': {
'channel_id': channel_id_raw,
'channel': channel_raw,
'channel_url': channel_url_raw,
'decoded_channel_url': channel_url
},
'example_url': f'{request.url_root}?channel_url=https://www.youtube.com/@username&api_key=YOUR_API_KEY&format=Atom',
'usage': {
'channel_id': 'UC... (YouTube Channel ID)',
'channel_id': 'UC... (YouTube Channel ID, 24 karakter)',
'channel': '@username veya username',
'channel_url': 'https://www.youtube.com/@username veya https://www.youtube.com/channel/UC...',
'format': 'Atom veya Rss (varsayılan: Atom)',
'max_items': 'Maksimum transcript sayısı (varsayılan: 10, maksimum: 100)',
'api_key': 'API key query parametresi olarak eklenmelidir (RSS okuyucular header gönderemez)'
}
}), 400
return jsonify({
'error': error_msg,
'received_params': {
'channel_id': channel_id_raw,
'channel': channel_raw,
'channel_url': channel_url_raw,
'decoded_channel_url': channel_url
},
'message': 'YouTube Channel ID UC ile başlayan 24 karakter olmalı (sadece alfanumerik ve alt çizgi)',
'usage': {
'channel_id': 'UC... (YouTube Channel ID, 24 karakter)',
'channel': '@username veya username',
'channel_url': 'https://www.youtube.com/@username veya https://www.youtube.com/channel/UC...',
'format': 'Atom veya Rss (varsayılan: Atom)',

96
test_curl.sh Executable file
View File

@@ -0,0 +1,96 @@
#!/bin/bash
# Test curl komutları - POST response debug için
# API key: demo_key_12345 (config/security.yaml'dan)
BASE_URL="http://localhost:5000"
API_KEY="demo_key_12345"
echo "=========================================="
echo "1. Health Check (API key gerekmez)"
echo "=========================================="
curl -X GET "${BASE_URL}/health" -v
echo -e "\n\n=========================================="
echo "2. Info Endpoint (API key gerekir)"
echo "=========================================="
curl -X GET "${BASE_URL}/info" \
-H "X-API-Key: ${API_KEY}" \
-v
echo -e "\n\n=========================================="
echo "3. Channel ID ile Feed (Atom format)"
echo "=========================================="
curl -X GET "${BASE_URL}/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&format=Atom&max_items=5" \
-H "X-API-Key: ${API_KEY}" \
-v
echo -e "\n\n=========================================="
echo "4. Channel Handle ile Feed (@kurzgesagt)"
echo " Not: İlk istekte 404 alınabilir (transcript henüz işlenmemiş)"
echo "=========================================="
curl -X GET "${BASE_URL}/?channel=@kurzgesagt&format=Atom&max_items=5" \
-H "X-API-Key: ${API_KEY}" \
-v
echo -e "\n\n=========================================="
echo "5. Channel URL ile Feed"
echo " Not: İlk istekte 404 alınabilir (transcript henüz işlenmemiş)"
echo "=========================================="
curl -X GET "${BASE_URL}/?channel_url=https://www.youtube.com/@kurzgesagt&format=Atom&max_items=5" \
-H "X-API-Key: ${API_KEY}" \
-v
echo -e "\n\n=========================================="
echo "6. RSS Format ile Feed"
echo "=========================================="
curl -X GET "${BASE_URL}/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&format=Rss&max_items=5" \
-H "X-API-Key: ${API_KEY}" \
-v
echo -e "\n\n=========================================="
echo "7. API Key olmadan (401 hatası beklenir)"
echo "=========================================="
curl -X GET "${BASE_URL}/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&format=Atom" \
-v
echo -e "\n\n=========================================="
echo "8. Geçersiz API Key ile (401 hatası beklenir)"
echo "=========================================="
curl -X GET "${BASE_URL}/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&format=Atom" \
-H "X-API-Key: invalid_key" \
-v
echo -e "\n\n=========================================="
echo "9. POST Response Debug Test (max_items=1, hızlı test)"
echo " Not: İlk istekte 404 alınabilir (transcript henüz işlenmemiş)"
echo "=========================================="
curl -X GET "${BASE_URL}/?channel_id=UCsXVk37bltHxD1rDPwtNM8Q&format=Atom&max_items=1" \
-H "X-API-Key: ${API_KEY}" \
-v \
-o /tmp/test_feed.xml
echo -e "\n\n=========================================="
echo "10. Brotli Decode Test (yeni video ile)"
echo " Not: İlk istekte 404 alınabilir (transcript henüz işlenmemiş)"
echo "=========================================="
curl -X GET "${BASE_URL}/?channel_id=UCsXVk37bltHxD1rDPwtNM8Q&format=Atom&max_items=1" \
-H "X-API-Key: ${API_KEY}" \
-v
echo -e "\n\n=========================================="
echo "Test tamamlandı!"
echo ""
echo "✅ Brotli desteği eklendi (requirements.txt)"
echo " YouTube response'ları artık otomatik decode edilecek"
echo ""
echo "POST response debug dosyaları: output/flaresolverr_debug/"
echo " - post_response_*.txt (her POST response)"
echo " - post_response_error_*.txt (JSONDecodeError durumunda)"
echo ""
echo "⚠️ Docker container'ı yeniden build etmeniz gerekiyor:"
echo " sudo docker compose down"
echo " sudo docker compose build --no-cache"
echo " sudo docker compose up -d"
echo "=========================================="