Zum Hauptinhalt springen

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

EigenschaftWert
Queueimports-youtube
tries0 (deaktiviert -- nur maxExceptions/retryUntil steuern Failure)
maxExceptions3
retryUntil48 Stunden
Backoff30s, 2min, 10min
UniqueNein
TriggerProcessWatcherImportRun, imports:retry-failed

Ablauf

  1. Import-Run und Workspace validieren (Favorites/Admin-Workspace blockiert)
  2. Quota-Pause prüfen -- bei aktiver Pause: release() mit Delay
  3. EnsureSocialProfileFromUrl aufrufen (Netzwerk/API-Calls)
  4. Duplikat-Check: Profil bereits im Workspace?
  5. Watcher + WatcherSource in DB-Transaktion erstellen
  6. Run-Counts inkrementieren (processed, added, failed, already_in_workspace)
  7. WatcherImportProgress Event broadcasten
  8. Bei Completion: Run-Status auf finished setzen

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: WatcherImportFailure wird 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

EigenschaftWert
Queueimports-youtube-video
Tries2
Backoff1 Stunde
UniqueNein
TriggerScheduler (youtube:sync-video-stats) / Admin-Button

Ablauf

  1. SocialProfile laden und Platform-Guard (nur YouTube)
  2. YouTubeVideoSync Record laden/erstellen
  3. Channel-Daten via API abrufen (uploads Playlist ID)
  4. Delta-Sync: Video-Count vergleichen -- bei Gleichstand Playlist-Fetch überspringen
  5. Full-Sync: Alle Videos der Playlist abrufen (max 50, letzte 12 Monate)
  6. Videos per upsert() in youtube_videos speichern
  7. Thumbnails herunterladen und lokal speichern
  8. Tägliche Statistiken in youtube_video_daily_metrics speichern
  9. Gelöschte Videos als is_removed markieren

Modi

ModusBeschreibung
delta (Standard)Nur neue Videos seit last_seen_video_id, Stats für bekannte Videos
fullAlle 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_days zurücksetzen, YouTubeVideoSyncCompleted Event 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

EigenschaftWert
Queueyoutube-related-channels
Tries0 (nur maxExceptions/retryUntil steuern Failure)
maxExceptions5
retryUntil24 Stunden
Timeout300s
Backoff5min, 15min, 30min (eskalierend)
UniqueJa (ShouldBeUnique, 2h Lock)
MiddlewareRateLimited('youtube-research')
TriggerUser-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: Dispatcht DetectProfileLanguage, released Job mit 5min Delay
  • Attempt >= 2: Faehrt ohne AI-Daten fort (kein endloses Warten)
  • AI-Daten vorhanden: Sofort weiter

Ablauf

  1. Profil laden, AI-Enhancer Prerequisite pruefen
  2. Status auf running setzen
  3. Suchqueries aus Channel-Daten generieren (Titel, Description, Topics, AI Keywords)
  4. YouTube Search API aufrufen (bis zu 8 Strategien, max 150 Ergebnisse)
  5. Relevance-Scores berechnen (min. 15 Punkte)
  6. Top 25 Kanaele auswaehlen
  7. Unbekannte Kanaele importieren (Admin-Workspace) oder in pending_you_tube_channel_imports queuen
  8. YouTubeRelatedChannel Beziehungen speichern
  9. RelatedProfilesCalculated Event broadcasten

Suchstrategien

PrioritätStrategieBeschreibung
1Title Keywords + Primary TopicSpezifischste Suche
2Title Keywords onlyBreitere Suche
3First Word + TopicFallback
4First Word onlyNoch breiter
5Additional TopicsWeitere Themen
6Description KeywordsBio-basiert
7AI KeywordsGemini-generierte semantische Keywords (neu)
8Title + CountryLokalisiert

Relevance-Scoring (max 100 Punkte)

KriteriumPunkteBeschreibung
Exists in Postbox35Kann sofort angezeigt werden
Subscriber Bonus0-20Mehr Abonnenten = relevanter
Favorites Bonus0-10Häufig favorisiert in Postbox
Position Bonus1-20Frühere Suchergebnisse bevorzugt
Topic Match10Passendes YouTube-Topic
Title Similarity0-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_imports gespeichert statt importiert
  • Genereller Fehler: Status failed, Error-Report, RelatedProfilesCalculated mit Fehler broadcasten

ProcessPendingYouTubeImports

Verarbeitet YouTube-Channel-Imports die bei FindRelatedYouTubeChannels wegen Quota-Erschöpfung nicht importiert werden konnten.

Location: app/Jobs/YouTube/ProcessPendingYouTubeImports.php

EigenschaftWert
Queueimports-youtube
Tries1
Timeout120s
UniqueNein
TriggerScheduler (alle 5 min)

Ablauf

  1. Abgelaufene/Zombie-Imports bereinigen (max age, max retries)
  2. Batch von 10 Pending-Imports laden (älteste zuerst)
  3. Pro Import: Profil existiert bereits? Direkt Beziehung erstellen
  4. Sonst: Channel importieren, Admin-Workspace-Watcher erstellen
  5. Bei Quota-Error: Batch abbrechen, retry_count inkrementieren
  6. Wenn alle Imports eines Profils fertig: Status auf completed + RecalculateRelatedChannelScores dispatchen

Fehlerbehandlung

  • Quota-Error: Batch sofort stoppen, retry_count erhöhen
  • Max Retries erreicht: Error-Report, Pending-Import wird bei nächstem Cleanup gelöscht
  • Orphaned Status: Profile mit pending_imports Status aber ohne Pending-Imports werden auf completed gesetzt

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

EigenschaftWert
Queueyoutube-related-channels
Tries1
Timeout120s
UniqueNein
TriggerProcessPendingYouTubeImports

Scoring (max 100 Punkte)

KriteriumPunkte
Exists in Postbox (Base)40
Subscriber Bonus0-25
Title Similarity0-5
Search/Topic Match10
Position Bonus10
"- 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

EigenschaftWert
Queueyoutube-related-channels
Tries5
Backoff1min, 5min, 10min, 30min, 1h
UniqueNein
TriggerFindRelatedYouTubeChannels

Ablauf

  1. Prüfen ob SocialProfile mit external_id existiert
  2. Wenn nicht: release(300) -- Profile noch nicht importiert
  3. Wenn ja: Placeholder-Row updaten oder neue Beziehung erstellen
  4. Duplikate bereinigen