DownloadBrandLogos.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. <?php
  2. namespace App\Console\Commands;
  3. /*
  4. * Artisan-команда: php artisan brands:download-logos
  5. *
  6. * Скачивает логотипы марок автомобилей из simple-icons CDN и Wikimedia Commons
  7. * и сохраняет в storage/app/public/makes/logos/.
  8. * Обновляет поле logo в dict_values для совпавших марок (раздел code=makes).
  9. *
  10. * Повторный запуск безопасен: уже скачанные файлы не перезаписываются.
  11. * Добавьте --force чтобы принудительно перескачать все логотипы.
  12. */
  13. use App\Models\DictSection;
  14. use App\Models\DictValue;
  15. use Illuminate\Console\Command;
  16. use Illuminate\Support\Facades\Http;
  17. use Illuminate\Support\Facades\Storage;
  18. use Illuminate\Support\Str;
  19. class DownloadBrandLogos extends Command
  20. {
  21. protected $signature = 'brands:download-logos {--force : Перезаписать существующие файлы}';
  22. protected $description = 'Скачивает логотипы марок автомобилей из simple-icons CDN и Wikimedia';
  23. // Карта: название марки → прямой URL для скачивания SVG
  24. // simple-icons CDN для большинства, Wikimedia для остальных
  25. private array $logoUrls = [
  26. 'Toyota' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/toyota.svg',
  27. 'Nissan' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/nissan.svg',
  28. 'Mitsubishi' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/mitsubishi.svg',
  29. 'Honda' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/honda.svg',
  30. 'Subaru' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/subaru.svg',
  31. 'Mazda' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/mazda.svg',
  32. 'Suzuki' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/suzuki.svg',
  33. 'Lexus' => 'https://upload.wikimedia.org/wikipedia/commons/7/75/Lexus.svg',
  34. 'Infiniti' => 'https://upload.wikimedia.org/wikipedia/commons/c/c3/Infiniti_logo.svg',
  35. 'BMW' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/bmw.svg',
  36. 'Mercedes-Benz' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/mercedes.svg',
  37. 'Audi' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/audi.svg',
  38. 'Volkswagen' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/volkswagen.svg',
  39. 'Porsche' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/porsche.svg',
  40. 'Land Rover' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/landrover.svg',
  41. 'Jeep' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/jeep.svg',
  42. 'Ford' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/ford.svg',
  43. 'Chevrolet' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/chevrolet.svg',
  44. 'Kia' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/kia.svg',
  45. 'Hyundai' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/hyundai.svg',
  46. 'Volvo' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/volvo.svg',
  47. 'Skoda' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/skoda.svg',
  48. 'Renault' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/renault.svg',
  49. 'Tesla' => 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/tesla.svg',
  50. 'Chery' => 'https://upload.wikimedia.org/wikipedia/commons/b/b6/Chery_logo.svg',
  51. 'Geely' => 'https://upload.wikimedia.org/wikipedia/commons/d/d4/Geely_logo.svg',
  52. ];
  53. public function handle(): int
  54. {
  55. $section = DictSection::where('code', 'makes')->first();
  56. if (! $section) {
  57. $this->error('Раздел справочника "makes" не найден.');
  58. return 1;
  59. }
  60. Storage::disk('public')->makeDirectory('makes/logos');
  61. $makes = DictValue::where('section_id', $section->id)
  62. ->whereNull('parent_id')
  63. ->get(['id', 'value', 'logo']);
  64. $this->info("Найдено {$makes->count()} марок. Начинаю загрузку...");
  65. $downloaded = 0;
  66. $skipped = 0;
  67. foreach ($makes as $make) {
  68. $url = $this->logoUrls[$make->value] ?? null;
  69. if (! $url) {
  70. $this->line(" ⚠ {$make->value}: нет источника в карте логотипов");
  71. continue;
  72. }
  73. $filename = 'makes/logos/'.Str::slug($make->value).'.svg';
  74. // Пропускаем если файл уже есть и не --force
  75. if (! $this->option('force') && $make->logo && Storage::disk('public')->exists($make->logo)) {
  76. $this->line(" ✓ {$make->value}: пропущено (уже есть)");
  77. $skipped++;
  78. continue;
  79. }
  80. try {
  81. $response = Http::timeout(15)
  82. ->withHeaders(['User-Agent' => 'TochaApp/1.0 (logo-downloader; contact@tocha.ru)'])
  83. ->get($url);
  84. if (! $response->successful()) {
  85. $this->warn(" ✗ {$make->value}: HTTP {$response->status()} → {$url}");
  86. continue;
  87. }
  88. Storage::disk('public')->put($filename, $response->body());
  89. $make->update(['logo' => $filename]);
  90. $this->info(" ↓ {$make->value}: сохранён → {$filename}");
  91. $downloaded++;
  92. } catch (\Throwable $e) {
  93. $this->warn(" ✗ {$make->value}: {$e->getMessage()}");
  94. }
  95. }
  96. $this->info("Готово: скачано {$downloaded}, пропущено {$skipped}.");
  97. return 0;
  98. }
  99. }