2016-10-10 10:57:39 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
2016-10-13 11:11:10 +02:00
|
|
|
use App\Item;
|
2016-10-10 10:57:39 +02:00
|
|
|
use DateTime;
|
|
|
|
use GuzzleHttp\Client;
|
|
|
|
|
|
|
|
class TMDB {
|
|
|
|
|
|
|
|
private $client;
|
|
|
|
private $apiKey;
|
2016-11-28 08:48:12 +01:00
|
|
|
private $translation;
|
2016-10-10 10:57:39 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the API Key for TMDB and create an instance of Guzzle.
|
|
|
|
*/
|
|
|
|
public function __construct()
|
|
|
|
{
|
|
|
|
$this->apiKey = config('app.TMDB_API_KEY');
|
2016-11-28 08:48:12 +01:00
|
|
|
$this->translation = config('app.TRANSLATION');
|
2016-10-10 10:57:39 +02:00
|
|
|
$this->client = new Client(['base_uri' => 'http://api.themoviedb.org/']);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-11-28 08:48:12 +01:00
|
|
|
* Search TMDB by 'title'.
|
2016-10-10 10:57:39 +02:00
|
|
|
*
|
|
|
|
* @param $title
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function search($title)
|
|
|
|
{
|
2016-11-28 08:48:12 +01:00
|
|
|
$response = $this->client->get('/3/search/multi', [
|
2016-11-23 16:24:34 +01:00
|
|
|
'query' => [
|
|
|
|
'api_key' => $this->apiKey,
|
|
|
|
'query' => $title,
|
2016-11-28 08:48:12 +01:00
|
|
|
'language' => strtolower($this->translation)
|
2016-11-23 16:24:34 +01:00
|
|
|
]
|
|
|
|
]);
|
2016-10-10 10:57:39 +02:00
|
|
|
|
2016-11-23 16:24:34 +01:00
|
|
|
return $this->createItems($response);
|
2016-10-10 10:57:39 +02:00
|
|
|
}
|
2016-10-13 11:11:10 +02:00
|
|
|
|
2016-10-14 12:15:54 +02:00
|
|
|
public function trending()
|
|
|
|
{
|
2016-10-14 13:25:52 +02:00
|
|
|
return $this->searchTrendingOrUpcoming('popular');
|
|
|
|
}
|
2016-10-14 12:15:54 +02:00
|
|
|
|
2016-10-14 13:25:52 +02:00
|
|
|
public function upcoming()
|
|
|
|
{
|
|
|
|
return $this->searchTrendingOrUpcoming('upcoming');
|
2016-10-14 12:15:54 +02:00
|
|
|
}
|
|
|
|
|
2016-10-13 11:11:10 +02:00
|
|
|
/**
|
|
|
|
* Search TMDB for recommendations and similar movies.
|
|
|
|
*
|
2016-11-28 08:48:12 +01:00
|
|
|
* @param $mediaType
|
2016-10-13 11:11:10 +02:00
|
|
|
* @param $tmdbID
|
|
|
|
* @return \Illuminate\Support\Collection
|
|
|
|
*/
|
2016-11-28 08:48:12 +01:00
|
|
|
public function suggestions($mediaType, $tmdbID)
|
2016-10-13 11:11:10 +02:00
|
|
|
{
|
2016-11-28 08:48:12 +01:00
|
|
|
$recommendations = $this->searchSuggestions($mediaType, $tmdbID, 'recommendations');
|
|
|
|
$similar = $this->searchSuggestions($mediaType, $tmdbID, 'similar');
|
2016-10-13 11:11:10 +02:00
|
|
|
|
|
|
|
$items = $recommendations->merge($similar);
|
|
|
|
|
|
|
|
$inDB = Item::all('tmdb_id')->toArray();
|
|
|
|
|
|
|
|
// Remove movies which already are in database.
|
|
|
|
return $items->filter(function($item) use ($inDB) {
|
|
|
|
return ! in_array($item['tmdb_id'], array_column($inDB, 'tmdb_id'));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-11-28 08:48:12 +01:00
|
|
|
* @param $mediaType
|
2016-10-13 11:11:10 +02:00
|
|
|
* @param $tmdbID
|
|
|
|
* @param $type
|
|
|
|
*
|
|
|
|
* @return \Illuminate\Support\Collection
|
|
|
|
*/
|
2016-11-28 08:48:12 +01:00
|
|
|
private function searchSuggestions($mediaType, $tmdbID, $type)
|
2016-10-13 11:11:10 +02:00
|
|
|
{
|
2016-11-28 08:48:12 +01:00
|
|
|
$response = $this->client->get('/3/' . $mediaType . '/' . $tmdbID . '/' . $type, [
|
|
|
|
'query' => [
|
|
|
|
'api_key' => $this->apiKey,
|
|
|
|
'language' => strtolower($this->translation)
|
|
|
|
]
|
|
|
|
]);
|
2016-10-18 09:57:47 +02:00
|
|
|
|
2016-11-28 08:48:12 +01:00
|
|
|
return collect($this->createItems($response, $mediaType));
|
2016-10-13 11:11:10 +02:00
|
|
|
}
|
|
|
|
|
2016-10-14 13:25:52 +02:00
|
|
|
/**
|
2016-10-14 14:52:04 +02:00
|
|
|
* Search TMDB for current popular or upcoming movies.
|
2016-10-14 13:25:52 +02:00
|
|
|
*
|
|
|
|
* @param $type
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function searchTrendingOrUpcoming($type)
|
|
|
|
{
|
2016-11-28 08:48:12 +01:00
|
|
|
$response = $this->client->get('/3/movie/' . $type, [
|
|
|
|
'query' => [
|
|
|
|
'api_key' => $this->apiKey,
|
|
|
|
'language' => strtolower($this->translation)
|
|
|
|
]
|
|
|
|
]);
|
2016-10-14 13:25:52 +02:00
|
|
|
|
2016-11-28 11:52:00 +01:00
|
|
|
$items = collect($this->createItems($response, 'movie'));
|
2016-10-14 13:25:52 +02:00
|
|
|
$allID = $items->pluck('tmdb_id');
|
|
|
|
|
2016-10-19 09:53:35 +02:00
|
|
|
// Get all movies from trendig / upcoming which already in database.
|
2016-10-14 13:25:52 +02:00
|
|
|
$inDB = Item::whereIn('tmdb_id', $allID)->get()->toArray();
|
|
|
|
|
2016-10-19 09:53:35 +02:00
|
|
|
// Remove all inDB movies from trending / upcoming.
|
2016-10-14 13:25:52 +02:00
|
|
|
$filtered = $items->filter(function($item) use ($inDB) {
|
|
|
|
return ! in_array($item['tmdb_id'], array_column($inDB, 'tmdb_id'));
|
|
|
|
});
|
|
|
|
|
|
|
|
$merged = $filtered->merge($inDB);
|
|
|
|
|
2016-10-14 14:52:04 +02:00
|
|
|
// Reset array keys to display inDB items first.
|
2016-10-14 13:25:52 +02:00
|
|
|
return array_values($merged->reverse()->toArray());
|
|
|
|
}
|
|
|
|
|
2016-10-13 11:11:10 +02:00
|
|
|
/**
|
|
|
|
* @param $response
|
|
|
|
* @return array
|
|
|
|
*/
|
2016-11-28 08:48:12 +01:00
|
|
|
private function createItems($response, $type = null)
|
2016-10-13 11:11:10 +02:00
|
|
|
{
|
|
|
|
$items = [];
|
|
|
|
$response = json_decode($response->getBody());
|
|
|
|
|
|
|
|
foreach($response->results as $result) {
|
2016-11-28 08:48:12 +01:00
|
|
|
// Suggestions doesn't deliver 'media type' by default
|
|
|
|
$mediaType = $type ?: $result->media_type;
|
|
|
|
|
|
|
|
if($mediaType == 'person') continue;
|
|
|
|
|
|
|
|
$dtime = DateTime::createFromFormat('Y-m-d', (array_key_exists('release_date', $result)
|
|
|
|
? ($result->release_date ?: '1970-12-1')
|
|
|
|
: ($result->first_air_date ?: '1970-12-1')
|
|
|
|
));
|
|
|
|
|
|
|
|
// 'name' is from tv, 'title' from movies
|
2016-10-13 11:11:10 +02:00
|
|
|
$items[] = [
|
|
|
|
'tmdb_id' => $result->id,
|
2016-11-28 08:48:12 +01:00
|
|
|
'title' => array_key_exists('name', $result) ? $result->name : $result->title,
|
|
|
|
'original_title' => array_key_exists('name', $result) ? $result->original_name : $result->original_title,
|
2016-10-13 11:11:10 +02:00
|
|
|
'poster' => $result->poster_path,
|
2016-11-28 08:48:12 +01:00
|
|
|
'media_type' => $mediaType,
|
2016-10-13 11:11:10 +02:00
|
|
|
'released' => $dtime->getTimestamp(),
|
2016-10-17 10:57:42 +02:00
|
|
|
'genre' => $this->parseGenre($result->genre_ids),
|
2016-11-28 08:48:12 +01:00
|
|
|
'episodes' => [],
|
2016-10-13 11:11:10 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $items;
|
|
|
|
}
|
2016-10-17 10:57:42 +02:00
|
|
|
|
2016-10-18 10:54:10 +02:00
|
|
|
/**
|
|
|
|
* Get full movie details.
|
|
|
|
*
|
|
|
|
* @param $tmdb_id
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function movie($tmdb_id)
|
|
|
|
{
|
2016-11-28 08:48:12 +01:00
|
|
|
$response = $this->client->get('/3/movie/' . $tmdb_id, [
|
|
|
|
'query' => [
|
|
|
|
'api_key' => $this->apiKey,
|
|
|
|
'language' => strtolower($this->translation)
|
|
|
|
]
|
|
|
|
]);
|
2016-10-18 10:54:10 +02:00
|
|
|
|
2016-12-17 16:59:59 +01:00
|
|
|
if($this->hasLimitRemaining($response)) {
|
|
|
|
return json_decode($response->getBody());
|
|
|
|
}
|
|
|
|
|
|
|
|
// After 10 seconds the TMDB request limit is resetted.
|
|
|
|
sleep(10);
|
|
|
|
return $this->movie($tmdb_id);
|
2016-10-18 10:54:10 +02:00
|
|
|
}
|
|
|
|
|
2016-11-28 08:48:12 +01:00
|
|
|
/**
|
|
|
|
* Get current count of seasons.
|
|
|
|
*
|
|
|
|
* @param $result
|
|
|
|
* @return integer | null
|
|
|
|
*/
|
|
|
|
private function tvSeasonsCount($id, $mediaType)
|
|
|
|
{
|
|
|
|
if($mediaType == 'tv') {
|
|
|
|
$response = $this->client->get('/3/tv/' . $id, [
|
|
|
|
'query' => [
|
|
|
|
'api_key' => $this->apiKey,
|
|
|
|
'language' => strtolower($this->translation)
|
|
|
|
]
|
|
|
|
]);
|
|
|
|
|
|
|
|
$seasons = collect(json_decode($response->getBody())->seasons);
|
|
|
|
|
|
|
|
return $seasons->filter(function ($season) {
|
|
|
|
// We don't need pilots
|
|
|
|
return $season->season_number > 0;
|
|
|
|
})->count();
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all episodes of each season.
|
|
|
|
*
|
|
|
|
* @param $id
|
|
|
|
* @param $seasons
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function tvEpisodes($id)
|
|
|
|
{
|
|
|
|
$seasons = $this->tvSeasonsCount($id, 'tv');
|
|
|
|
$data = [];
|
|
|
|
|
|
|
|
for($i = 1; $i <= $seasons; $i++) {
|
|
|
|
$response = $this->client->get('/3/tv/' . $id . '/season/' . $i, [
|
|
|
|
'query' => [
|
|
|
|
'api_key' => $this->apiKey,
|
|
|
|
'language' => strtolower($this->translation)
|
|
|
|
]
|
|
|
|
]);
|
|
|
|
|
|
|
|
$data[$i] = json_decode($response->getBody());
|
|
|
|
}
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
2016-12-17 16:59:59 +01:00
|
|
|
/**
|
|
|
|
* Make a new request to TMDb to get the alternative titles.
|
|
|
|
*/
|
|
|
|
public function getAlternativeTitles($item)
|
|
|
|
{
|
|
|
|
$response = $this->client->get('/3/' . $item['media_type'] . '/' . $item['tmdb_id'] . '/alternative_titles', [
|
|
|
|
'query' => [
|
|
|
|
'api_key' => $this->apiKey
|
|
|
|
]
|
|
|
|
]);
|
|
|
|
|
|
|
|
if($this->hasLimitRemaining($response)) {
|
|
|
|
$body = json_decode($response->getBody());
|
|
|
|
|
|
|
|
if(property_exists($body, 'titles')) {
|
|
|
|
return $body->titles;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
// After 10 seconds the TMDB request limit is resetted.
|
|
|
|
sleep(10);
|
|
|
|
return $this->getAlternativeTitles($item);
|
|
|
|
}
|
|
|
|
|
2016-10-17 10:57:42 +02:00
|
|
|
/**
|
|
|
|
* Create genre string from genre_ids.
|
|
|
|
*
|
|
|
|
* @param $ids
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
private function parseGenre($ids)
|
|
|
|
{
|
|
|
|
$genre = [];
|
|
|
|
|
|
|
|
foreach($ids as $id) {
|
2016-10-18 10:54:10 +02:00
|
|
|
$genre[] = isset($this->genreList()[$id]) ? $this->genreList()[$id] : '';
|
2016-10-17 10:57:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return implode($genre, ', ');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Current genre list from TMDb.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function genreList()
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
28 => 'Action',
|
2016-10-18 09:57:47 +02:00
|
|
|
12 => 'Adventure',
|
|
|
|
16 => 'Animation',
|
|
|
|
35 => 'Comedy',
|
2016-10-17 10:57:42 +02:00
|
|
|
80 => 'Crime',
|
2016-10-18 09:57:47 +02:00
|
|
|
99 => 'Documentary',
|
|
|
|
18 => 'Drama',
|
2016-10-17 10:57:42 +02:00
|
|
|
10751 => 'Family',
|
2016-10-18 09:57:47 +02:00
|
|
|
14 => 'Fantasy',
|
2016-10-17 10:57:42 +02:00
|
|
|
36 => 'History',
|
|
|
|
27 => 'Horror',
|
|
|
|
10402 => 'Music',
|
|
|
|
9648 => 'Mystery',
|
|
|
|
10749 => 'Romance',
|
2016-10-18 09:57:47 +02:00
|
|
|
878 => 'Sci-Fi',
|
|
|
|
10770 => 'TV Movie',
|
2016-10-17 10:57:42 +02:00
|
|
|
53 => 'Thriller',
|
|
|
|
10752 => 'War',
|
|
|
|
37 => 'Western',
|
2016-11-24 08:52:14 +01:00
|
|
|
10759 => 'Action & Adventure',
|
|
|
|
10762 => 'Kids',
|
|
|
|
10763 => 'News',
|
|
|
|
10764 => 'Reality',
|
|
|
|
10765 => 'Sci-Fi & Fantasy',
|
|
|
|
10766 => 'Soap',
|
|
|
|
10767 => 'Talk',
|
|
|
|
10768 => 'War & Politics',
|
2016-10-17 10:57:42 +02:00
|
|
|
];
|
|
|
|
}
|
2016-12-17 16:59:59 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $response
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
private function hasLimitRemaining($response)
|
|
|
|
{
|
|
|
|
return (int) $response->getHeader('X-RateLimit-Remaining')[0] > 1;
|
|
|
|
}
|
2016-10-10 10:57:39 +02:00
|
|
|
}
|