YouTube Jobs
Alle Jobs die mit YouTube-Daten arbeiten: Channel-Imports, Video-Sync und Related-Channel-Discovery.
ImportWatcherFromUrl
Importiert einen einzelnen YouTube-Kanal per URL, erstellt ein SocialProfile und verknüpft einen Watcher im Ziel-Workspace.
Location: app/Jobs/ImportWatcherFromUrl.php
| Eigenschaft | Wert |
|---|---|
| Queue | imports-youtube |
| tries | 0 (deaktiviert -- nur maxExceptions/retryUntil steuern Failure) |
| maxExceptions | 3 |
| retryUntil | 48 Stunden |
| Backoff | 30s, 2min, 10min |
| Unique | Nein |
| Trigger | ProcessWatcherImportRun, imports:retry-failed |
Ablauf
- Import-Run und Workspace validieren (Favorites/Admin-Workspace blockiert)
- Quota-Pause prüfen -- bei aktiver Pause:
release()mit Delay EnsureSocialProfileFromUrlaufrufen (Netzwerk/API-Calls)- Duplikat-Check: Profil bereits im Workspace?
Watcher+WatcherSourcein DB-Transaktion erstellen- Run-Counts inkrementieren (
processed,added,failed,already_in_workspace) WatcherImportProgressEvent broadcasten- Bei Completion: Run-Status auf
finishedsetzen
Fehlerbehandlung
- Quota-Error (429 / quotaExceeded): Run wird pausiert bis morgen 02:00 UTC, Job wird per
release()verzögert -- zählt nicht als Exception und nicht als Attempt ($tries = 0) - Normaler Fehler:
WatcherImportFailurewird erstellt, Run-Counts aktualisiert - Permanent Failed: Nach 3 Exceptions oder 48h:
failed()Method loggt Warning und erstellt Failure-Eintrag
Wichtig: $tries = 0 ist nötig, weil release() bei Quota-Pausen normalerweise den Attempt-Counter des Workers inkrementiert. Mit $tries = 3 führte das nach wenigen Quota-Pausen zu MaxAttemptsExceededException. Jetzt steuern ausschließlich maxExceptions (echte Fehler) und retryUntil (48h Zeitlimit) die Failure-Entscheidung.
// Quota-Erkennung: HTTP 429 + YouTube-spezifische Fehlermeldungen
private function isQuotaError(Throwable $e): bool
{
if ((int) $e->getCode() === 429) return true;
$message = strtolower($e->getMessage());
return str_contains($message, 'quotaexceeded')
|| str_contains($message, 'rate limit');
}
SyncYouTubeVideoStats
Synchronisiert YouTube-Video-Statistiken (Views, Likes, Comments) für ein Profil mit aktiviertem Auto-Sync.
Location: app/Jobs/YouTube/SyncYouTubeVideoStats.php
| Eigenschaft | Wert |
|---|---|
| Queue | imports-youtube-video |
| Tries | 2 |
| Backoff | 1 Stunde |
| Unique | Nein |
| Trigger | Scheduler (youtube:sync-video-stats) / Admin-Button |
Ablauf
SocialProfileladen und Platform-Guard (nur YouTube)YouTubeVideoSyncRecord laden/erstellen- Channel-Daten via API abrufen (uploads Playlist ID)
- Delta-Sync: Video-Count vergleichen -- bei Gleichstand Playlist-Fetch überspringen
- Full-Sync: Alle Videos der Playlist abrufen (max 50, letzte 12 Monate)
- Videos per
upsert()inyoutube_videosspeichern - Thumbnails herunterladen und lokal speichern
- Tägliche Statistiken in
youtube_video_daily_metricsspeichern - Gelöschte Videos als
is_removedmarkieren
Modi
| Modus | Beschreibung |
|---|---|
delta (Standard) | Nur neue Videos seit last_seen_video_id, Stats für bekannte Videos |
full | Alle Videos aus Playlist abrufen, inkl. last_full_sync_at Update |
Fail-Streak Mechanismus
Nach 7 aufeinanderfolgenden Fehltagen wird Auto-Sync automatisch deaktiviert:
private const MAX_CONSECUTIVE_FAIL_DAYS = 7;
// Nur 1x pro Tag inkrementieren
if ($lastFailDate !== $today) {
$consecutiveDays = $sync->consecutive_fail_days + 1;
// ...
if ($consecutiveDays >= self::MAX_CONSECUTIVE_FAIL_DAYS) {
$this->disableAutoSyncDueToFailStreak($sync);
}
}
Fehlerbehandlung
- Erfolg:
consecutive_fail_dayszurücksetzen,YouTubeVideoSyncCompletedEvent broadcasten - Fehler: Fail-Streak inkrementieren, bei Threshold Auto-Sync deaktivieren + Error-Report
- Playlist 404 (graceful): Wenn die YouTube API HTTP 404 für die Uploads-Playlist zurückgibt (Channel gelöscht, privat oder nicht verfügbar), wird der Playlist-Fetch übersprungen und eine Warning geloggt. Der Fail-Streak wird nicht inkrementiert, da es sich um einen permanenten Zustand handelt. Der Job kann trotzdem fortfahren und Daily-Stats für bereits bekannte Videos synchronisieren.
FindRelatedYouTubeChannels
Sucht ähnliche YouTube-Kanäle über die YouTube Search API und speichert Beziehungen mit Relevance-Scores.
Location: app/Jobs/YouTube/FindRelatedYouTubeChannels.php
| Eigenschaft | Wert |
|---|---|
| Queue | youtube-related-channels |
| Tries | 0 (nur maxExceptions/retryUntil steuern Failure) |
| maxExceptions | 5 |
| retryUntil | 24 Stunden |
| Timeout | 300s |
| Backoff | 5min, 15min, 30min (eskalierend) |
| Unique | Ja (ShouldBeUnique, 2h Lock) |
| Middleware | RateLimited('youtube-research') |
| Trigger | User-Klick (Livewire) / youtube:auto-fill-related-channels |
AI-Enhancer Prerequisite
Vor der Suche prueft der Job, ob AI-Daten vorliegen. AI-Keywords verbessern die Suchqualitaet signifikant:
private function shouldWaitForAiEnhancer(SocialProfile $profile): bool
- Attempt 1, kein
detected_at/ai_keywords: DispatchtDetectProfileLanguage, released Job mit 5min Delay - Attempt >= 2: Faehrt ohne AI-Daten fort (kein endloses Warten)
- AI-Daten vorhanden: Sofort weiter
Ablauf
- Profil laden, AI-Enhancer Prerequisite pruefen
- Status auf
runningsetzen - Suchqueries aus Channel-Daten generieren (Titel, Description, Topics, AI Keywords)
- YouTube Search API aufrufen (bis zu 8 Strategien, max 150 Ergebnisse)
- Relevance-Scores berechnen (min. 15 Punkte)
- Top 25 Kanaele auswaehlen
- Unbekannte Kanaele importieren (Admin-Workspace) oder in
pending_you_tube_channel_importsqueuen YouTubeRelatedChannelBeziehungen speichernRelatedProfilesCalculatedEvent broadcasten
Suchstrategien
| Priorität | Strategie | Beschreibung |
|---|---|---|
| 1 | Title Keywords + Primary Topic | Spezifischste Suche |
| 2 | Title Keywords only | Breitere Suche |
| 3 | First Word + Topic | Fallback |
| 4 | First Word only | Noch breiter |
| 5 | Additional Topics | Weitere Themen |
| 6 | Description Keywords | Bio-basiert |
| 7 | AI Keywords | Gemini-generierte semantische Keywords (neu) |
| 8 | Title + Country | Lokalisiert |
Relevance-Scoring (max 100 Punkte)
| Kriterium | Punkte | Beschreibung |
|---|---|---|
| Exists in Postbox | 35 | Kann sofort angezeigt werden |
| Subscriber Bonus | 0-20 | Mehr Abonnenten = relevanter |
| Favorites Bonus | 0-10 | Häufig favorisiert in Postbox |
| Position Bonus | 1-20 | Frühere Suchergebnisse bevorzugt |
| Topic Match | 10 | Passendes YouTube-Topic |
| Title Similarity | 0-5 | Ähnlicher Kanalname |
Minimum-Schwelle: MIN_RELEVANCE_SCORE = 15 — Kandidaten darunter werden verworfen.
Fehlerbehandlung
- AI-Prerequisite: Job released sich selbst mit Delay, kein Fehler
- Quota-Error: Channel wird in
pending_you_tube_channel_importsgespeichert statt importiert - Genereller Fehler: Status
failed, Error-Report,RelatedProfilesCalculatedmit Fehler broadcasten
ProcessPendingYouTubeImports
Verarbeitet YouTube-Channel-Imports die bei FindRelatedYouTubeChannels wegen Quota-Erschöpfung nicht importiert werden konnten.
Location: app/Jobs/YouTube/ProcessPendingYouTubeImports.php
| Eigenschaft | Wert |
|---|---|
| Queue | imports-youtube |
| Tries | 1 |
| Timeout | 120s |
| Unique | Nein |
| Trigger | Scheduler (alle 5 min) |
Ablauf
- Abgelaufene/Zombie-Imports bereinigen (max age, max retries)
- Batch von 10 Pending-Imports laden (älteste zuerst)
- Pro Import: Profil existiert bereits? Direkt Beziehung erstellen
- Sonst: Channel importieren, Admin-Workspace-Watcher erstellen
- Bei Quota-Error: Batch abbrechen,
retry_countinkrementieren - Wenn alle Imports eines Profils fertig: Status auf
completed+RecalculateRelatedChannelScoresdispatchen
Fehlerbehandlung
- Quota-Error: Batch sofort stoppen,
retry_counterhöhen - Max Retries erreicht: Error-Report, Pending-Import wird bei nächstem Cleanup gelöscht
- Orphaned Status: Profile mit
pending_importsStatus aber ohne Pending-Imports werden aufcompletedgesetzt
RecalculateRelatedChannelScores
Berechnet Relevance-Scores für Related YouTube Channels neu, nachdem Subscriber-Daten durch Imports verfügbar geworden sind.
Location: app/Jobs/YouTube/RecalculateRelatedChannelScores.php
| Eigenschaft | Wert |
|---|---|
| Queue | youtube-related-channels |
| Tries | 1 |
| Timeout | 120s |
| Unique | Nein |
| Trigger | ProcessPendingYouTubeImports |
Scoring (max 100 Punkte)
| Kriterium | Punkte |
|---|---|
| Exists in Postbox (Base) | 40 |
| Subscriber Bonus | 0-25 |
| Title Similarity | 0-5 |
| Search/Topic Match | 10 |
| Position Bonus | 10 |
| "- Topic" De-Rank | -50% |
"- Topic" De-Rank: YouTube-Sammelkanaele (z.B. "Gaming - Topic") erhalten nach allen Bonus-Berechnungen eine 50%-Reduktion. Erkennung via str_ends_with(title, '- Topic').
Batch-Update
Score-Updates nutzen eine einzige SQL-Query mit CASE/WHEN statt N einzelner UPDATEs (sicher fuer 200k+ Profile):
UPDATE youtube_related_channels
SET relevance_score = (CASE WHEN id = ? THEN ? ... END)::smallint, updated_at = ?
WHERE id IN (?, ...)
ResolveRelatedChannelRelationship
Löst eine einzelne Placeholder-Beziehung (related_social_profile_id = 0) auf, sobald der importierte Channel als SocialProfile existiert.
Location: app/Jobs/YouTube/ResolveRelatedChannelRelationship.php
| Eigenschaft | Wert |
|---|---|
| Queue | youtube-related-channels |
| Tries | 5 |
| Backoff | 1min, 5min, 10min, 30min, 1h |
| Unique | Nein |
| Trigger | FindRelatedYouTubeChannels |
Ablauf
- Prüfen ob
SocialProfilemitexternal_idexistiert - Wenn nicht:
release(300)-- Profile noch nicht importiert - Wenn ja: Placeholder-Row updaten oder neue Beziehung erstellen
- Duplikate bereinigen