validate([ 'keyword' => 'required|string', 'limit' => 'nullable|integer', 'token' => 'required|string', ]); // Tambahkan "lang:id" ke keyword $keyword = trim($request->keyword) . ' lang:id'; // Limit default 100 jika tidak diisi $limit = $request->limit ?? 100; // Nama file output berdasarkan keyword $safeKeyword = preg_replace('/[^a-z0-9_]/i', '', str_replace(' ', '_', strtolower($request->keyword))); $outputFile = "{$safeKeyword}.csv"; // Jalankan command Artisan::call('scrap:tweets', [ '--keyword' => $keyword, '--limit' => $limit, '--output' => $outputFile, '--token' => $request->token, ]); $outputText = Artisan::output(); $mainPath = public_path("tweets-data/{$outputFile}"); $nestedPath = public_path("tweets-data/tweets-data/{$outputFile}"); // Jika file tidak ditemukan di lokasi utama, coba cek nested folder if (!file_exists($mainPath) && file_exists($nestedPath)) { try { File::ensureDirectoryExists(public_path('tweets-data')); File::move($nestedPath, $mainPath); @rmdir(public_path('tweets-data/tweets-data')); } catch (\Exception $e) { $outputText .= "\n\n⚠️ Gagal memindahkan file dari nested folder: " . $e->getMessage(); } } // Terakhir, pastikan file sudah ada if (!file_exists($mainPath)) { $outputText .= "\n\n❌ Gagal menemukan file hasil scraping di lokasi manapun."; return back()->with('status', nl2br($outputText)); } return back() ->with('status', nl2br($outputText)) ->with('csv_filename', $outputFile); } }