Files
2026-07-14 00:01:48 +03:00

175 lines
6.9 KiB
PHP
Raw Permalink 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.
<?php
namespace App\Console\Commands;
use App\Models\Anime;
use App\Models\Genre;
use App\Services\DeepSeekService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class FillAnimeMeta extends Command
{
protected $signature = 'animexe:fill-anime-meta
{--limit=0 : Max anime sayısı (0 = sınırsız, hepsi)}
{--anime= : Sadece belirli bir anime ID işle}
{--force : Dolu alanlar olsa bile yeniden doldur}
{--dry-run : Gerçekten kaydetme, sadece ne yapılacağını göster}';
protected $description = 'Eksik meta verisi olan animeleri DeepSeek AI ile toplu doldurur (description, genres, studio, year, vb.)';
public function handle(DeepSeekService $ai): int
{
// PHP zaman aşımını kaldır — tüm animeler işlenene kadar çalışsın
set_time_limit(0);
if (!$ai->isConfigured()) {
$this->error('DeepSeek API anahtarı ayarlanmamış. Admin > Ayarlar > deepseek_api_key');
return self::FAILURE;
}
$limit = (int) $this->option('limit');
$animeId = $this->option('anime');
$force = $this->option('force');
$dry = $this->option('dry-run');
// İşlenecek animeleri belirle
if ($animeId) {
$animes = Anime::where('id', $animeId)->with('genres')->get();
} else {
$query = Anime::with('genres');
if (!$force) {
$query->where(function ($q) {
$q->whereNull('description')->orWhere('description', '')
->orWhereNull('release_year')
->orWhereNull('studio')->orWhere('studio', '')
->orWhereNull('type')->orWhere('type', '')
->orWhereNull('status')->orWhere('status', '');
})->orDoesntHave('genres');
}
$query->orderBy('id');
if ($limit > 0) $query->limit($limit);
$animes = $query->get();
}
if ($animes->isEmpty()) {
$this->info('İşlenecek anime bulunamadı (tüm alanlar dolu).');
Log::channel('daily')->info('[FillAnimeMeta] İşlenecek anime yok.');
return self::SUCCESS;
}
$this->info("Toplam {$animes->count()} anime işlenecek" . ($dry ? ' (dry-run)' : '') . '...');
Log::channel('daily')->info("[FillAnimeMeta] Başladı. {$animes->count()} anime, limit={$limit}, force=" . ($force ? 'evet' : 'hayır'));
$done = 0;
$skipped = 0;
$failed = 0;
foreach ($animes as $anime) {
$missing = $this->missingFields($anime);
if (!$force && empty($missing)) {
$this->line(" <fg=gray>ATLA</> {$anime->title} — tüm alanlar dolu");
$skipped++;
continue;
}
$label = $force ? 'tüm alanlar' : implode(', ', $missing);
$this->line(" <fg=cyan>İŞLE</> [{$anime->id}] {$anime->title} — eksik: {$label}");
if ($dry) {
$done++;
continue;
}
$meta = $ai->generateAnimeMeta($anime->title, $anime->title_jp ?? '');
if (!$meta) {
$this->warn(" <fg=red>HATA</> {$anime->title} — AI boş yanıt döndürdü");
Log::channel('daily')->warning("[FillAnimeMeta] HATA [{$anime->id}] {$anime->title}: AI boş yanıt");
$failed++;
sleep(3);
continue;
}
// Sadece boş alanları doldur (force modunda hepsini güncelle)
$updates = [];
$fillIfEmpty = function (string $field, $value) use ($anime, $force, &$updates) {
if ($value === null || $value === '') return;
if ($force || empty($anime->$field)) {
$updates[$field] = $value;
}
};
$fillIfEmpty('description', $meta['description'] ?? null);
$fillIfEmpty('release_year', $meta['release_year'] ?? null);
$fillIfEmpty('studio', $meta['studio'] ?? null);
$fillIfEmpty('type', $meta['type'] ?? null);
$fillIfEmpty('status', $meta['status'] ?? null);
$fillIfEmpty('title_en', $meta['title_en'] ?? null);
$fillIfEmpty('title_jp', $meta['title_jp'] ?? null);
// Rating: sadece boşsa veya 0 ise doldur
if (!empty($meta['rating']) && ($force || !$anime->rating)) {
$updates['rating'] = min(10, max(0, (float) $meta['rating']));
}
if (!empty($updates)) {
$anime->update($updates);
}
// Genres: boşsa ekle
if (!empty($meta['genres']) && ($force || $anime->genres->isEmpty())) {
$genreIds = [];
foreach ($meta['genres'] as $genreName) {
$genre = Genre::firstOrCreate(
['name' => $genreName],
['slug' => \Illuminate\Support\Str::slug($genreName)]
);
$genreIds[] = $genre->id;
}
if ($genreIds) {
$force ? $anime->genres()->sync($genreIds) : $anime->genres()->syncWithoutDetaching($genreIds);
}
}
$updatedFields = array_keys($updates);
if (!empty($meta['genres']) && ($force || $anime->genres->isEmpty())) {
$updatedFields[] = 'genres(' . implode(',', $meta['genres'] ?? []) . ')';
}
$summary = empty($updatedFields) ? 'Yeni alan yok' : implode(', ', $updatedFields);
$this->info(" <fg=green>✓ OK</> [{$anime->id}] {$anime->title}{$summary}");
Log::channel('daily')->info("[FillAnimeMeta] OK [{$anime->id}] {$anime->title}{$summary}");
$done++;
// API rate limit — DeepSeek'i boğma
sleep(2);
}
$summary = "Tamamlandı: {$done} işlendi, {$skipped} atlandı, {$failed} hata.";
$this->info($summary);
Log::channel('daily')->info("[FillAnimeMeta] {$summary}");
return $failed > 0 ? self::FAILURE : self::SUCCESS;
}
private function missingFields(Anime $anime): array
{
$missing = [];
if (empty($anime->description)) $missing[] = 'description';
if (empty($anime->release_year)) $missing[] = 'release_year';
if (empty($anime->studio)) $missing[] = 'studio';
if (empty($anime->type)) $missing[] = 'type';
if (empty($anime->status)) $missing[] = 'status';
if (!$anime->rating) $missing[] = 'rating';
if (empty($anime->title_en)) $missing[] = 'title_en';
if ($anime->genres->isEmpty()) $missing[] = 'genres';
return $missing;
}
}