baseUrl = rtrim(config('movies.omdb.base_url', 'https://www.omdbapi.com/'), '/'); $this->apiKey = config('movies.omdb.api_key'); $this->cacheTtl = (int) config('movies.omdb.cache_ttl', 3600); } public function search(string $query, int $page = 1): array { $query = trim($query); if ($query === '') { return ['results' => collect(), 'total' => 0, 'page' => 1, 'has_more' => false]; } $cacheKey = sprintf('omdb.search.%s.p%s', md5($query), $page); $data = Cache::remember($cacheKey, $this->cacheTtl, function () use ($query, $page) { $response = Http::acceptJson() ->timeout(6) ->retry(3, 300) ->get($this->baseUrl, [ 'apikey' => $this->apiKey, 's' => $query, 'type' => 'movie', 'page' => $page, ]); if ($response->failed()) { abort(502, 'Couldn’t reach OMDb. Please try again.'); } return $response->json(); }); $results = collect(); $total = 0; $hasMore = false; if (isset($data['Response']) && $data['Response'] === 'True' && isset($data['Search'])) { $total = (int) ($data['totalResults'] ?? 0); $hasMore = $page * 10 < $total; // OMDb pages of 10 $results = collect($data['Search'])->map(function ($item) { return [ 'provider' => 'omdb', 'provider_id' => $item['imdbID'] ?? null, 'title' => $item['Title'] ?? null, 'year' => $item['Year'] ?? null, 'poster_url' => ($item['Poster'] ?? 'N/A') !== 'N/A' ? $item['Poster'] : null, ]; }); } return [ 'results' => $results, 'total' => $total, 'page' => $page, 'has_more' => $hasMore, ]; } public function details(string $id): array { $id = trim($id); $cacheKey = sprintf('omdb.details.%s', $id); $data = Cache::remember($cacheKey, $this->cacheTtl, function () use ($id) { $response = Http::acceptJson() ->timeout(6) ->retry(3, 300) ->get($this->baseUrl, [ 'apikey' => $this->apiKey, 'i' => $id, 'plot' => 'full', 'type' => 'movie', ]); if ($response->failed()) { abort(502, 'Couldn’t reach OMDb. Please try again.'); } return $response->json(); }); if (!isset($data['Response']) || $data['Response'] !== 'True') { // Map explicit OMDb errors that indicate provider-side issues differently if needed // Default to not found for details when OMDb says False abort(404, 'Movie not found in OMDb'); } // Normalize to internal structure $genres = $this->splitList($data['Genre'] ?? ''); $actors = $this->splitList($data['Actors'] ?? ''); $directors = $this->splitList($data['Director'] ?? ''); $studios = $this->splitList($data['Production'] ?? ''); $countries = $this->splitList($data['Country'] ?? ''); $languages = $this->splitList($data['Language'] ?? ''); return [ 'provider' => 'omdb', 'provider_id' => $data['imdbID'] ?? null, 'external_ids' => [ 'imdb' => $data['imdbID'] ?? null, 'omdb' => $data['imdbID'] ?? null, ], 'title' => $data['Title'] ?? null, 'original_title' => $data['Title'] ?? null, 'description' => $data['Plot'] ?? null, 'poster_url' => ($data['Poster'] ?? 'N/A') !== 'N/A' ? $data['Poster'] : null, 'backdrop_url' => null, 'genres' => $genres, 'rating' => $data['Rated'] ?? null, 'release_date' => $this->parseDate($data['Released'] ?? null), 'year' => isset($data['Year']) ? (int) substr($data['Year'], 0, 4) : null, 'runtime' => $this->parseRuntime($data['Runtime'] ?? null), 'actors' => $actors, 'directors' => $directors, 'studios' => $studios, 'countries' => $countries, 'languages' => $languages, ]; } protected function splitList(?string $value): array { if (!$value || $value === 'N/A') return []; return collect(explode(',', $value)) ->map(fn($v) => trim($v)) ->filter() ->values() ->all(); } protected function parseRuntime(?string $runtime): ?int { if (!$runtime) return null; if (preg_match('/(\d+) min/i', $runtime, $m)) { return (int) $m[1]; } return null; } protected function parseDate(?string $date): ?string { if (!$date || $date === 'N/A') return null; // OMDb uses formats like "01 Jan 2000" $ts = strtotime($date); return $ts ? date('Y-m-d', $ts) : null; } }