Skip to content
This repository was archived by the owner on Jan 1, 2021. It is now read-only.

Commit a57d7b5

Browse files
authored
Merge pull request #2 from Radarr/develop
Improved IMDB Code. Results should load much faster. Overhauled mapping api and database structure. IMDB Code is also in house now, meaning no more python shenanigans. Mapping API, Database structure and returned JSON is now much more sensible. A lot of request validation was also added in preparation of Radarr integration. Fixed exception when no movie is currently added to your Radarr library.
2 parents 98c0830 + 65a6ebc commit a57d7b5

18 files changed

Lines changed: 937 additions & 171 deletions

app/Event.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace App;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
class Event extends Model
8+
{
9+
protected $connection = 'mappings_mysql';
10+
11+
protected $fillable = ["type", "mappings_id", "ip"];
12+
13+
public $timestamps = false;
14+
15+
public function mapping()
16+
{
17+
return $this->hasOne("App\Mapping", "id", "mappings_id");
18+
}
19+
20+
public function toArray()
21+
{
22+
$arr = parent::toArray();
23+
24+
$arr["mapping"] = $this->mapping->toArray();
25+
unset($arr["ip"]);
26+
$arr["mapping"]["movie"] = MappingMovie::find($arr["mapping"]["tmdbid"]);
27+
return $arr;
28+
}
29+
}
30+
31+
abstract class EventType extends Enum {
32+
const AddedMapping = 0;
33+
const ApproveMapping = 1;
34+
const DisapproveMapping = 2;
35+
const LockedMapping = 3;
36+
}
37+
38+
abstract class Enum {
39+
static function getKeys(){
40+
$class = new ReflectionClass(get_called_class());
41+
return array_keys($class->getConstants());
42+
}
43+
}

app/Exceptions/Handler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Handler extends NewExceptionHandler
2222
protected $dontReport = [
2323
\Illuminate\Auth\AuthenticationException::class,
2424
\Illuminate\Auth\Access\AuthorizationException::class,
25-
//\Symfony\Component\HttpKernel\Exception\HttpException::class,
25+
\Symfony\Component\HttpKernel\Exception\HttpException::class,
2626
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
2727
\Illuminate\Session\TokenMismatchException::class,
2828
\Illuminate\Validation\ValidationException::class,

app/Helpers/Helper.php

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,43 @@
66
use Carbon\Carbon;
77
use Illuminate\Support\Facades\Cache;
88
use Illuminate\Support\Facades\Log;
9+
use Illuminate\Support\Facades\DB;
10+
911

1012
class Helper
1113
{
1214

13-
public static function get_from_imdb_py($verb, $var = "", $rememberMinutes = 60*5)
15+
public static function get_from_imdb_py($path, $selection, $rememberMinutes = 60*5)
1416
{
15-
Cache::flush();
16-
return Cache::remember("imdb.$verb.$var", Carbon::now()->addMinutes($rememberMinutes), function() use ($verb, $var){
17-
Log::info("Calling python IMDBAPI.py $verb $var");
18-
$listIds = exec("python IMDBAPI.py $verb $var");
19-
Log::info("Result from IMDB script: $listIds");
20-
$exploded = explode(",", $listIds);
21-
$orderedListIds = array();
22-
foreach($exploded as $id) {
23-
$orderedListIds[] = str_ireplace("'", "", $id);
24-
}
25-
$count = count($orderedListIds);
26-
Log::info("IMDB Movies Count: $count");
27-
$movies = Movie::whereIn("imdb_id", $orderedListIds)->get()->toArray();
28-
$response = array();
29-
foreach ($movies as $movie)
17+
//Cache::flush();
18+
return Cache::remember("imdb.$path", Carbon::now()->addMinutes($rememberMinutes), function() use ($path, $selection){
19+
Log::info("Starting up imdb api");
20+
$resp = IMDBAPI::shared()->getJSON($path);
21+
//dd($resp);
22+
$ids = resolveMany($resp, $selection);
23+
24+
$fullIds = array();
25+
26+
foreach ($ids as $id)
3027
{
31-
$index = array_search($movie["imdb_id"], $orderedListIds);
32-
unset($orderedListIds[$index]);
33-
$response[$index] = $movie;
28+
$fullIds[] = rtrim(str_ireplace("/title/", "", $id), "/");
3429
}
35-
ksort($response);
3630

37-
$notFound = count($orderedListIds);
38-
if ($notFound > 0)
31+
$idStr = join("','", $fullIds);
32+
33+
$movies = DB::select("SELECT sub.* FROM (SELECT m.*, r.release_date as physical_release, r.note as physical_release_note, r.type from `movies` m LEFT JOIN release_dates r ON r.tmdbid = m.id AND r.type in (4,5,6) where m.`imdb_id` in ('$idStr') ORDER BY r.release_date) sub GROUP BY sub.id ORDER BY FIELD(imdb_id, '$idStr');");
34+
35+
$with_genres = array();
36+
37+
foreach ($movies as $movie)
3938
{
40-
Log::warning("Could not find $notFound movies! IMDBIDS are: ", array("missing_imdb_ids" => $orderedListIds));
39+
$movie->genres = explode(",", $movie->genres);
40+
$movie->adult = $movie->adult == 1;
41+
unset($movie->type);
42+
$with_genres[] = $movie;
4143
}
42-
return array_values($response);
44+
45+
return $with_genres;
4346
});
4447
}
4548

@@ -174,3 +177,5 @@ public static function generate_uuid_v4()
174177
}
175178

176179
}
180+
181+

app/Helpers/HelperFunctions.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
function resolveA(array $a, $path, $default = null)
4+
{
5+
$current = $a;
6+
$p = strtok($path, '.');
7+
8+
while ($p !== false) {
9+
if (!isset($current[$p])) {
10+
return $default;
11+
}
12+
$current = $current[$p];
13+
$p = strtok('.');
14+
}
15+
16+
return $current;
17+
}
18+
19+
function resolveMany(array $a, $path, $default = array())
20+
{
21+
$current = $a;
22+
$p = strtok($path, '.');
23+
24+
while ($p !== false) {
25+
if ($p == "!all")
26+
{
27+
$ret = array();
28+
$p = strtok('.');
29+
if ($p === false)
30+
{
31+
return $current;
32+
}
33+
foreach ($current as $value)
34+
{
35+
$ret[] = resolveMany($value, $p);
36+
37+
}
38+
return $ret;
39+
}
40+
else
41+
{
42+
if (!isset($current[$p])) {
43+
return $default;
44+
}
45+
$current = $current[$p];
46+
$p = strtok('.');
47+
}
48+
49+
}
50+
51+
return $current;
52+
}
53+
54+
?>

app/Helpers/IMDBAPI.php

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<?php
2+
/**
3+
* Created by PhpStorm.
4+
* User: leonardogalli
5+
* Date: 21.06.17
6+
* Time: 10:54
7+
*/
8+
9+
namespace App\Helpers;
10+
use Illuminate\Foundation\Testing\HttpException;
11+
use Illuminate\Support\Facades\Config;
12+
use GuzzleHttp\Client;
13+
14+
15+
final class IMDBAPI
16+
{
17+
private $API_KEY = "";
18+
private $API_SECRET = "";
19+
20+
private $client;
21+
22+
/**
23+
* Call this method to get singleton
24+
*
25+
* @return UserFactory
26+
*/
27+
public static function shared()
28+
{
29+
static $inst = null;
30+
if ($inst === null) {
31+
$inst = new IMDBAPI();
32+
}
33+
return $inst;
34+
}
35+
36+
/**
37+
* Private ctor so nobody else can instance it
38+
*
39+
*/
40+
private function __construct()
41+
{
42+
$this->API_KEY = Config::get("app.imdb_key");
43+
$this->API_SECRET = Config::get("app.imdb_secret");
44+
$this->client = new Client([]);
45+
}
46+
47+
public function getJSON($path = "", $queryItems = array(), $host = "api.imdbws.com")
48+
{
49+
try
50+
{
51+
$response = $this->request($path, $queryItems, "GET", $host, null);
52+
return json_decode($response->getBody(), true);
53+
}
54+
catch (\GuzzleHttp\Exception\ClientException $e) {
55+
$response = $e->getResponse();
56+
try
57+
{
58+
abort($response->getStatusCode(), json_decode($response->getBody(), true)["message"]);
59+
}
60+
catch (Exception $e)
61+
{
62+
abort($response->getStatusCode(), $response->getReasonPhrase());
63+
}
64+
65+
}
66+
67+
return $response;
68+
}
69+
70+
public function request($path = "", $queryItems = array(), $method = "GET", $host = "api.imdbws.com", $postBody = null)
71+
{
72+
$headers = ["user-agent" => "IMDb/7.2 (iPhone; iOS 10.2.1)"];
73+
$signedHeaders = [];
74+
75+
if ($path == "" || $path == null)
76+
{
77+
$path = "/";
78+
}
79+
80+
if (!(stripos($path, "/") === 0))
81+
{
82+
$path = "/" . $path;
83+
}
84+
85+
$query = $this->getCanocialQueryStr($queryItems);
86+
87+
$url = "https://$host$path?$query";
88+
$timeStr = strftime("%a, %d %b %Y %H:%M:%S %Z");
89+
$headers["x-amz-date"] = $timeStr;
90+
91+
$headers["x-amzn-authorization"] = $this->createRequestSignature($method, $host, $path, $query, $postBody, $headers);
92+
93+
//dd($headers);
94+
95+
$response = $this->client->request($method, $url, [
96+
"headers" => $headers,
97+
'allow_redirects' => false
98+
]);
99+
100+
$statusCode = $response->getStatusCode();
101+
102+
if ($statusCode == 301 || $statusCode == 302)
103+
{
104+
return $this->request(json_decode($response->getBody(), true)["link"], $queryItems, $method, $host, $postBody);
105+
}
106+
107+
return $response;
108+
}
109+
110+
private function createRequestSignature($method, $host, $path, $query, $postBody, $headers)
111+
{
112+
$signedHeaders = [];
113+
foreach ($headers as $key => $value)
114+
{
115+
if (stripos($key, "amz")!= false)
116+
{
117+
$signedHeaders[$key] = $value;
118+
}
119+
}
120+
121+
//$headersStr = join(";", $signedHeaders);
122+
123+
$cHeaders = "";
124+
$cHeaders .= "host:" . $host . "\n";
125+
126+
foreach ($signedHeaders as $key => $value) {
127+
$cHeaders .= $key . ":" . $value . "\n";
128+
}
129+
130+
$toSign = "$method\n$path\n$query\n$cHeaders\n";
131+
//var_dump($toSign);
132+
//dd($toSign);
133+
134+
$signature = base64_encode(hash_hmac("sha256", hash("sha256", $toSign, true), $this->API_SECRET, True));
135+
136+
return "AWS3 AWSAccessKeyId={$this->API_KEY},Algorithm=HmacSHA256,Signature=$signature,SignedHeaders=" . join(";", array_keys($signedHeaders));
137+
}
138+
139+
private function getCanocialQueryStr($queryItems)
140+
{
141+
$queryStr = "";
142+
foreach ($queryItems as $key => $value) {
143+
if (is_array($value))
144+
{
145+
foreach ($value as $v)
146+
{
147+
$queryStr .= $key . "=" . $v . "&";
148+
}
149+
}
150+
else
151+
{
152+
$queryStr .= $key . "=" . $value . "&";
153+
}
154+
155+
}
156+
157+
$queryStr = rtrim($queryStr, ",");
158+
return $queryStr;
159+
}
160+
161+
162+
}

app/Http/Controllers/API/DiscoverController.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function upcoming() {
2828
$resp = Cache::remember("discovery.upcoming", Carbon::now()->addHours(12), function (){
2929
return Movie::whereHas("release_dates", function($query) {
3030
$query->whereIn("type", array(4,5,6))->whereBetween("release_date", array(Carbon::now()->subWeek(), Carbon::now()->addWeeks(3)))->orderBy("release_date", "ASC");
31-
})->orderBy("popularity", "DESC")->get()->toArray();
31+
})->where("adult", "=", "0")->orderBy("popularity", "DESC")->get()->toArray();
3232
});
3333
return response()->json($resp);
3434
}
@@ -52,7 +52,12 @@ public function recommendations(Request $request) {
5252
{
5353
$ignoredIds = ",".$ignoredIds;
5454
}
55-
$movies_db = DB::select("SELECT mo.id, mo.popularity, mo.imdb_id, mo.title, mo.overview, mo.vote_average, mo.vote_count, mo.tagline, mo.poster_path, mo.release_date, mo.release_year, mo.trailer_key, mo.trailer_site, mo.backdrop_path, mo.homepage, mo.runtime, mo.countO FROM ( SELECT m.*, r.recommended_id, r.tmdbid, r.id as rid, count(m.id) as countO FROM movies m, recommendations r WHERE m.id = r.recommended_id AND r.tmdbid in ($ids) AND r.recommended_id not in ($ids$ignoredIds) GROUP BY m.id ) as mo;");
55+
56+
if ($ids == "" || $ids == null)
57+
{
58+
abort(422, "Please add some movies before using our recommendation engine :)");
59+
}
60+
$movies_db = DB::select("SELECT mo.id, mo.popularity, mo.imdb_id, mo.title, mo.overview, mo.vote_average, mo.vote_count, mo.tagline, mo.poster_path, mo.release_date, mo.release_year, mo.trailer_key, mo.trailer_site, mo.backdrop_path, mo.homepage, mo.runtime, mo.countO, mo.genres, mo.runtime, mo.adult FROM ( SELECT m.*, r.recommended_id, r.tmdbid, r.id as rid, count(m.id) as countO FROM movies m, recommendations r WHERE m.id = r.recommended_id AND r.tmdbid in ($ids) AND r.recommended_id not in ($ids$ignoredIds) AND m.adult = 0 GROUP BY m.id ) as mo;");
5661
$movies = json_decode(json_encode($movies_db), true);
5762

5863
$this->count_max = maximum($movies, "countO");
@@ -62,8 +67,17 @@ public function recommendations(Request $request) {
6267
usort($movies, array($this, "compare_score"));
6368

6469
$movies = array_slice($movies, 0, 30);
70+
$resp = [];
6571

66-
return response()->json($movies);
72+
foreach ($movies as $movie) {
73+
unset($movie["countO"]);
74+
$movie["genres"] = explode(",", $movie["genres"]);
75+
$movie["adult"] = $movie["adult"] == 1;
76+
$resp[] = $movie;
77+
}
78+
79+
80+
return response()->json($resp);
6781
}
6882

6983
function score ($elem)

0 commit comments

Comments
 (0)