162 lines
5.5 KiB
PHP
162 lines
5.5 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
namespace App\Modules\Movies\Services\Omdb;
|
|||
|
|
|
|||
|
|
use App\Modules\Movies\Services\Contracts\MovieProvider;
|
|||
|
|
use Illuminate\Support\Facades\Cache;
|
|||
|
|
use Illuminate\Support\Facades\Http;
|
|||
|
|
use Illuminate\Support\Collection;
|
|||
|
|
|
|||
|
|
class OmdbMovieProvider implements MovieProvider
|
|||
|
|
{
|
|||
|
|
protected string $baseUrl;
|
|||
|
|
protected ?string $apiKey;
|
|||
|
|
protected int $cacheTtl;
|
|||
|
|
|
|||
|
|
public function __construct()
|
|||
|
|
{
|
|||
|
|
$this->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;
|
|||
|
|
}
|
|||
|
|
}
|