false, 'Logger' => null, 'ReleaseImage' => null, 'Settings' => null, 'TMDb' => null, ]; $options += $defaults; $this->pdo = ($options['Settings'] instanceof Settings ? $options['Settings'] : new Settings()); $this->releaseImage = ($options['ReleaseImage'] instanceof \ReleaseImage ? $options['ReleaseImage'] : new \ReleaseImage($this->pdo)); $this->lookuplanguage = ($this->pdo->getSetting('lookuplanguage') != '') ? (string)$this->pdo->getSetting('lookuplanguage') : 'en'; $this->fanartapikey = $this->pdo->getSetting('fanarttvkey'); $this->imdburl = ($this->pdo->getSetting('imdburl') == 0 ? false : true); $this->movieqty = ($this->pdo->getSetting('maximdbprocessed') != '') ? $this->pdo->getSetting('maximdbprocessed') : 100; $this->searchEngines = true; $this->showPasswords = Releases::showPasswords($this->pdo); $this->debug = NN_DEBUG; $this->echooutput = ($options['Echo'] && NN_ECHOCLI && $this->pdo->cli); $this->imgSavePath = NN_COVERS . 'movies' . DS; $this->service = ''; if (NN_DEBUG || NN_LOGGING) { $this->debug = true; try { $this->debugging = new \Logger(); } catch (\LoggerException $error) { $this->_debug = false; } } } /** * Get info for a IMDB id. * * @param int $imdbId * * @return array|bool */ public function getMovieInfo($imdbId) { return $this->pdo->queryOneRow(sprintf("SELECT * FROM movieinfo WHERE imdbid = %d", $imdbId)); } /** * Get info for multiple IMDB id's. * * @param array $imdbIDs * * @return array */ public function getMovieInfoMultiImdb($imdbIDs) { return $this->pdo->query( sprintf(" SELECT DISTINCT movieinfo.*, releases.imdbid AS relimdb FROM movieinfo LEFT OUTER JOIN releases ON releases.imdbid = movieinfo.imdbid WHERE movieinfo.imdbid IN (%s)", str_replace( ',,', ',', str_replace( ['(,', ' ,', ', )', ',)'], '', implode(',', $imdbIDs) ) ) ), true, NN_CACHE_EXPIRY_MEDIUM ); } /** * Get movies for movie-list admin page. * * @param int $start * @param int $num * * @return array */ public function getRange($start, $num) { return $this->pdo->query( sprintf(' SELECT * FROM movieinfo ORDER BY createddate DESC %s', ($start === false ? '' : ' LIMIT ' . $num . ' OFFSET ' . $start) ) ); } /** * Get count of movies for movie-list admin page. * * @return int */ public function getCount() { $res = $this->pdo->queryOneRow('SELECT COUNT(id) AS num FROM movieinfo'); return ($res === false ? 0 : $res['num']); } /** * Get count of movies for movies browse page. * * @param $cat * @param $maxAge * @param array $excludedCats * * @return int */ public function getMovieCount($cat, $maxAge = -1, $excludedCats = []) { $catsrch = ''; if (count($cat) > 0 && $cat[0] != -1) { $catsrch = (new \Category(['Settings' => $this->pdo]))->getCategorySearch($cat); } $res = $this->pdo->queryOneRow( sprintf(" SELECT COUNT(DISTINCT r.imdbid) AS num FROM releases r INNER JOIN movieinfo m ON m.imdbid = r.imdbid WHERE r.nzbstatus = 1 AND r.imdbid != '0000000' AND m.cover = 1 AND m.title != '' AND r.passwordstatus %s AND %s %s %s %s ", $this->showPasswords, $this->getBrowseBy(), $catsrch, ($maxAge > 0 ? 'AND r.postdate > NOW() - INTERVAL ' . $maxAge . ' DAY' : ''), (count($excludedCats) > 0 ? ' AND r.categoryid NOT IN (' . implode(',', $excludedCats) . ')' : '') ) ); return ($res === false ? 0 : $res['num']); } /** * Get movie releases with covers for movie browse page. * * @param $cat * @param $start * @param $num * @param $orderBy * @param $maxAge * @param array $excludedCats * * @return bool|PDOStatement */ public function getMovieRange($cat, $start, $num, $orderBy, $maxAge = -1, $excludedCats = []) { $catsrch = ''; if (count($cat) > 0 && $cat[0] != -1) { $catsrch = (new \Category(['Settings' => $this->pdo]))->getCategorySearch($cat); } $order = $this->getMovieOrder($orderBy); $sql = sprintf(" SELECT GROUP_CONCAT(r.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_id, GROUP_CONCAT(r.rarinnerfilecount ORDER BY r.postdate DESC SEPARATOR ',') as grp_rarinnerfilecount, GROUP_CONCAT(r.haspreview ORDER BY r.postdate DESC SEPARATOR ',') AS grp_haspreview, GROUP_CONCAT(r.passwordstatus ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_password, GROUP_CONCAT(r.guid ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_guid, GROUP_CONCAT(rn.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_nfoid, GROUP_CONCAT(groups.name ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grpname, GROUP_CONCAT(r.searchname ORDER BY r.postdate DESC SEPARATOR '#') AS grp_release_name, GROUP_CONCAT(r.postdate ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_postdate, GROUP_CONCAT(r.size ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_size, GROUP_CONCAT(r.totalpart ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_totalparts, GROUP_CONCAT(r.comments ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_comments, GROUP_CONCAT(r.grabs ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grabs, GROUP_CONCAT(r.failed ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_failed, m.*, groups.name AS group_name, rn.id as nfoid FROM releases r LEFT OUTER JOIN groups ON groups.id = r.groupid LEFT OUTER JOIN releasenfo rn ON rn.releaseid = r.id INNER JOIN movieinfo m ON m.imdbid = r.imdbid WHERE r.nzbstatus = 1 AND r.imdbid != '0000000' AND m.title != '' AND r.passwordstatus %s AND %s %s %s %s GROUP BY m.imdbid ORDER BY %s %s %s", $this->showPasswords, $this->getBrowseBy(), $catsrch, ($maxAge > 0 ? 'AND r.postdate > NOW() - INTERVAL ' . $maxAge . 'DAY ' : '' ), (count($excludedCats) > 0 ? ' AND r.categoryid NOT IN (' . implode(',', $excludedCats) . ')' : ''), $order[0], $order[1], ($start === false ? '' : ' LIMIT ' . $num . ' OFFSET ' . $start) ); return $this->pdo->query($sql, true, NN_CACHE_EXPIRY_MEDIUM); } /** * Get the order type the user requested on the movies page. * * @param $orderBy * * @return array */ protected function getMovieOrder($orderBy) { $orderArr = explode('_', (($orderBy == '') ? 'MAX(r.postdate)' : $orderBy)); switch ($orderArr[0]) { case 'title': $orderField = 'm.title'; break; case 'year': $orderField = 'm.year'; break; case 'rating': $orderField = 'm.rating'; break; case 'posted': default: $orderField = 'MAX(r.postdate)'; break; } return [$orderField, ((isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1])) ? $orderArr[1] : 'desc')]; } /** * Order types for movies page. * * @return array */ public function getMovieOrdering() { return ['title_asc', 'title_desc', 'year_asc', 'year_desc', 'rating_asc', 'rating_desc']; } /** * @return string */ protected function getBrowseBy() { $browseBy = ' '; $browseByArr = ['title', 'director', 'actors', 'genre', 'rating', 'year', 'imdb']; foreach ($browseByArr as $bb) { if (isset($_REQUEST[$bb]) && !empty($_REQUEST[$bb])) { $bbv = stripslashes($_REQUEST[$bb]); if ($bb === 'rating') { $bbv .= '.'; } if ($bb === 'imdb') { $browseBy .= 'm.' . $bb . 'id = ' . $bbv . ' AND '; } else { $browseBy .= 'm.' . $bb . ' ' . $this->pdo->likeString($bbv, true, true) . ' AND '; } } } return $browseBy; } /** * @var null|TraktTv */ public $traktTv = null; /** * Get trailer using IMDB Id. * * @param int $imdbID * * @return bool|string */ public function getTrailer($imdbID) { if (!is_numeric($imdbID)) { return false; } $trailer = $this->pdo->queryOneRow("SELECT trailer FROM movieinfo WHERE imdbid = $imdbID and trailer != ''"); if ($trailer) { return $trailer['trailer']; } if (is_null($this->traktTv)) { $this->traktTv = new TraktTv(['Settings' => $this->pdo]); } $data = $this->traktTv->movieSummary('tt' . $imdbID, 'full,images'); if ($data) { $this->parseTraktTv($data); if (isset($data['trailer']) && !empty($data['trailer'])) { return $data['trailer']; } } $trailer = Utility::imdb_trailers($imdbID); if ($trailer) { $this->pdo->queryExec( 'UPDATE movieinfo SET trailer = ' . $this->pdo->escapeString($trailer) . ' WHERE imdbid = ' . $imdbID ); return $trailer; } return false; } /** * Parse trakt info, insert into DB. * * @param array $data * * @return mixed|void */ public function parseTraktTv(&$data) { if (!isset($data['ids']['imdb']) || empty($data['ids']['imdb'])) { return false; } if (isset($data['trailer']) && !empty($data['trailer'])) { $data['trailer'] = str_ireplace( 'http://', 'https://', str_ireplace('watch?v=', 'embed/', $data['trailer']) ); return $data['trailer']; } $cover = 0; if (is_file($this->imgSavePath . $data['ids']['imdb'] . '-cover.jpg')) { $cover = 1; } else { $link = $this->checkTraktValue($data['images']['poster']['thumb']); if ($link) { $cover = $this->releaseImage->saveImage($data['ids']['imdb'] . '-cover', $link, $this->imgSavePath); } } $this->update([ 'genres' => $this->checkTraktValue($data['genres']), 'imdbid' => $this->checkTraktValue(str_ireplace('tt', '', $data['ids']['imdb'])), 'language' => $this->checkTraktValue($data['language']), 'plot' => $this->checkTraktValue($data['overview']), 'rating' => round($this->checkTraktValue($data['rating']), 1), 'tagline' => $this->checkTraktValue($data['tagline']), 'title' => $this->checkTraktValue($data['title']), 'tmdbid' => $this->checkTraktValue($data['ids']['tmdb']), 'trailer' => $this->checkTraktValue($data['trailer']), 'cover' => $cover, 'year' => $this->checkTraktValue($data['year']) ]); } /** * Checks if the value is set and not empty, returns it, else empty string. * * @param mixed $value * * @return string */ private function checkTraktValue($value) { if (is_array($value)) { $temp = ''; foreach($value as $val) { if (!is_array($val) && !is_object($val)) { $temp .= (string)$val; } } $value = $temp; } return (isset($value) && !empty($value) ? $value : ''); } /** * Create click-able links to IMDB actors/genres/directors/etc.. * * @param $data * @param $field * * @return string */ public function makeFieldLinks($data, $field) { if (!isset($data[$field]) || $data[$field] == '') { return ''; } $tmpArr = explode(', ', $data[$field]); $newArr = []; $i = 0; foreach ($tmpArr as $ta) { if (trim($ta) == '') { continue; } if ($i > 5) { break; } //only use first 6 $newArr[] = '' . $ta . ''; $i++; } return implode(', ', $newArr); } /** * Get array of column keys, for inserting / updating. * @return array */ public function getColumnKeys() { return [ 'actors','backdrop','cover','director','genre','imdbid','language', 'plot','rating','tagline','title','tmdbid', 'trailer','type','year', 'traktid' ]; } /** * Update movie on movie-edit page. * * @param array $values Array of keys/values to update. See $validKeys * @return int|bool */ public function update(array $values) { if (!count($values)) { return false; } $validKeys = $this->getColumnKeys(); $query = [ '0' => 'INSERT INTO movieinfo (updateddate, createddate, ', '1' => ' VALUES (NOW(), NOW(), ', '2' => 'ON DUPLICATE KEY UPDATE updateddate = NOW(), ' ]; $found = 0; foreach ($values as $key => $value) { if (in_array($key, $validKeys) && !empty($value)) { $found++; $query[0] .= "$key, "; if (in_array($key, ['genre', 'language'])) { $value = substr($value, 0, 64); } $value = $this->pdo->escapeString($value); $query[1] .= "$value, "; $query[2] .= "$key = $value, "; } } if (!$found) { return false; } foreach ($query as $key => $value) { $query[$key] = rtrim($value, ', '); } return $this->pdo->queryInsert($query[0] . ') ' . $query[1] . ') ' . $query[2]); } /** * Check if a variable is set and not a empty string. * * @param $variable * * @return string */ protected function checkVariable(&$variable) { if (isset($variable) && $variable != '') { return true; } return false; } /** * Returns a tmdb, imdb or trakt variable, the one that is set. Empty string if both not set. * * @param string $variable1 * @param string $variable2 * @param string $variable3 * * @return string */ protected function setTmdbImdbTraktVar(&$variable1, &$variable2, &$variable3) { if ($this->checkVariable($variable1)) { return $variable1; } elseif ($this->checkVariable($variable2)) { return $variable2; } elseif ($this->checkVariable($variable3)) { return $variable3; } return ''; } /** * Fetch IMDB/TMDB/TRAKT info for the movie. * * @param $imdbId * * @return bool */ public function updateMovieInfo($imdbId) { if ($this->echooutput && $this->service !== '') { $this->pdo->log->doEcho($this->pdo->log->primary("Fetching IMDB info from TMDB and/or Trakt using IMDB id: " . $imdbId)); } // Check TMDB for IMDB info. $tmdb = $this->fetchTMDBProperties($imdbId); // Check IMDB for movie info. $imdb = $this->fetchIMDBProperties($imdbId); // Check TRAKT for movie info $trakt = $this->fetchTraktTVProperties($imdbId); if (!$imdb && !$tmdb && !$trakt) { return false; } // Check FanArt.tv for cover and background images. $fanart = $this->fetchFanartTVProperties($imdbId); $mov = []; $mov['cover'] = $mov['backdrop'] = $mov['banner'] = $movieID = 0; $mov['type'] = $mov['director'] = $mov['actors'] = $mov['language'] = ''; $mov['imdbid'] = $imdbId; $mov['tmdbid'] = (!isset($tmdb['tmdbid']) || $tmdb['tmdbid'] == '') ? 0 : $tmdb['tmdbid']; $mov['traktid'] = $trakt['id']; // Prefer Fanart.tv cover over TRAKT, TRAKT over TMDB and TMDB over IMDB. if ($this->checkVariable($fanart['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId . '-cover', $fanart['cover'], $this->imgSavePath); } else if ($this->checkVariable($trakt['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId . '-cover', $trakt['cover'], $this->imgSavePath); } else if ($this->checkVariable($tmdb['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId . '-cover', $tmdb['cover'], $this->imgSavePath); } else if ($this->checkVariable($imdb['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId . '-cover', $imdb['cover'], $this->imgSavePath); } // Backdrops. if ($this->checkVariable($fanart['backdrop'])) { $mov['backdrop'] = $this->releaseImage->saveImage($imdbId . '-backdrop', $fanart['backdrop'], $this->imgSavePath, 1920, 1024); } else if ($this->checkVariable($tmdb['backdrop'])) { $mov['backdrop'] = $this->releaseImage->saveImage($imdbId . '-backdrop', $tmdb['backdrop'], $this->imgSavePath, 1920, 1024); } // Banner if ($this->checkVariable($fanart['banner'])) { $mov['banner'] = $this->releaseImage->saveImage($imdbId . '-banner', $fanart['banner'], $this->imgSavePath); } $mov['title'] = $this->setTmdbImdbTraktVar($imdb['title'] , $tmdb['title'], $trakt['title']); $mov['rating'] = $this->setTmdbImdbTraktVar($imdb['rating'] , $tmdb['rating'], $trakt['rating']); $mov['plot'] = $this->setTmdbImdbTraktVar($imdb['plot'] , $tmdb['plot'], $trakt['overview']); $mov['tagline'] = $this->setTmdbImdbTraktVar($imdb['tagline'], $tmdb['tagline'], $trakt['tagline']); $mov['year'] = $this->setTmdbImdbTraktVar($imdb['year'] , $tmdb['year'], $trakt['year']); $mov['genre'] = $this->setTmdbImdbTraktVar($imdb['genre'] , $tmdb['genre'], $trakt['genres']); if ($this->checkVariable($imdb['type'])) { $mov['type'] = $imdb['type']; } if ($this->checkVariable($imdb['director'])) { $mov['director'] = (is_array($imdb['director'])) ? implode(', ', array_unique($imdb['director'])) : $imdb['director']; } if ($this->checkVariable($imdb['actors'])) { $mov['actors'] = (is_array($imdb['actors'])) ? implode(', ', array_unique($imdb['actors'])) : $imdb['actors']; } if ($this->checkVariable($imdb['language'])) { $mov['language'] = (is_array($imdb['language'])) ? implode(', ', array_unique($imdb['language'])) : $imdb['language']; } if (is_array($mov['genre'])) { $mov['genre'] = implode(', ', array_unique($mov['genre'])); } if (is_array($mov['type'])) { $mov['type'] = implode(', ', array_unique($mov['type'])); } $mov['title'] = html_entity_decode($mov['title'] , ENT_QUOTES, 'UTF-8'); $mov['title'] = str_replace(['/', '\\'], '', $mov['title']); $movieID = $this->update([ 'actors' => html_entity_decode($mov['actors'] , ENT_QUOTES, 'UTF-8'), 'backdrop' => $mov['backdrop'], 'cover' => $mov['cover'], 'director' => html_entity_decode($mov['director'], ENT_QUOTES, 'UTF-8'), 'genre' => html_entity_decode($mov['genre'] , ENT_QUOTES, 'UTF-8'), 'imdbid' => $mov['imdbid'], 'language' => html_entity_decode($mov['language'], ENT_QUOTES, 'UTF-8'), 'plot' => html_entity_decode(preg_replace('/\s+See full summary ยป/', ' ', $mov['plot']), ENT_QUOTES, 'UTF-8'), 'rating' => round($mov['rating'], 1), 'tagline' => html_entity_decode($mov['tagline'] , ENT_QUOTES, 'UTF-8'), 'title' => $mov['title'], 'tmdbid' => $mov['tmdbid'], 'type' => html_entity_decode(ucwords(preg_replace('/[\.\_]/', ' ', $mov['type'])), ENT_QUOTES, 'UTF-8'), 'year' => $mov['year'], 'traktid' => $mov['traktid'] ]); if ($this->echooutput && $this->service !== '') { $this->pdo->log->doEcho( $this->pdo->log->headerOver(($movieID !== 0 ? 'Added/updated movie: ' : 'Nothing to update for movie: ')) . $this->pdo->log->primary($mov['title'] . ' (' . $mov['year'] . ') - ' . $mov['imdbid'] ) ); } return ($movieID === 0 ? false : true); } /** * Fetch FanArt.tv backdrop / cover / title. * * @param $imdbId * * @return bool|array */ protected function fetchFanartTVProperties($imdbId) { if ($this->fanartapikey != '') { $buffer = Utility::getUrl(['url' => 'https://webservice.fanart.tv/v3/movies/' . 'tt' . $imdbId . '?api_key=' . $this->fanartapikey , 'verifycert' => false]); if ($buffer !== false) { $art = json_decode($buffer, true); if (isset($art['status']) && $art['status'] === 'error') { return false; } $ret = []; if ($this->checkVariable($art['moviebackground'][0]['url'])) { $ret['backdrop'] = $art['moviebackground'][0]['url']; } else if ($this->checkVariable($art['moviethumb'][0]['url'])) { $ret['backdrop'] = $art['moviethumb'][0]['url']; } if ($this->checkVariable($art['movieposter'][0]['url'])) { $ret['cover'] = $art['movieposter'][0]['url']; } if ($this->checkVariable($art['moviebanner'][0]['url'])) { $ret['banner'] = $art['moviebanner'][0]['url']; } if (isset($ret['backdrop']) && isset($ret['cover'])) { $ret['title'] = $imdbId; if (isset($art['name'])) { $ret['title'] = $art['name']; } if ($this->echooutput) { $this->pdo->log->doEcho($this->pdo->log->alternateOver("Fanart Found ") . $this->pdo->log->headerOver($ret['title']), true); } return $ret; } } } return false; } /** * Fetch info for IMDB id from TMDB. * * @param $imdbId * @param bool $text * * @return array|bool */ public function fetchTMDBProperties($imdbId, $text = false) { $lookupId = ($text === false ? 'tt' . $imdbId : $imdbId); $tmdb = new TMDB($this->pdo->getSetting('tmdbkey')); try { $tmdbLookup = $tmdb->getMovie($lookupId); } catch (exception $e) { return false; } /*$status = $tmdbLookup->get('status_code'); if (!$status || (isset($status) && $status !== 1)) { return false; }*/ $ret = []; $ret['title'] = $tmdbLookup->get('title'); if ($this->currentTitle !== '') { // Check the similarity. similar_text($this->currentTitle, $ret['title'], $percent); if ($percent < 40) { if ($this->debug) { $this->debugging->log( get_class(), __FUNCTION__, 'Found (' . $ret['title'] . ') from TMDB, but it\'s only ' . $percent . '% similar to (' . $this->currentTitle . ')', \Logger::LOG_INFO ); } return false; } } $ret['tmdbid'] = $tmdbLookup->getID(); $ImdbID = str_replace('tt', '', $tmdbLookup->get('imdb_id')); $ret['imdb_id'] = $ImdbID; $vote = $tmdbLookup->getVoteAverage(); if (isset($vote)) { $ret['rating'] = ($vote == 0) ? '' : $vote; } $overview = $tmdbLookup->get('overview'); if (isset($overview)) { $ret['plot'] = $overview; } $tagline = $tmdbLookup->getTagline(); if (isset($tagline)) { $ret['tagline'] = $tagline; } $released = $tmdbLookup->get('release_date'); if (isset($released)) { $ret['year'] = date("Y", strtotime($released)); } $genresa = $tmdbLookup->get('genres'); if (isset($genresa) && sizeof($genresa) > 0) { $genres = []; foreach ($genresa as $genre) { $genres[] = $genre['name']; } $ret['genre'] = $genres; } $posterp = $tmdbLookup->getPoster(); if (isset($posterp) && sizeof($posterp > 0)) { $ret['cover'] = "http://image.tmdb.org/t/p/w185" . $posterp; } $backdrop = $tmdbLookup->get('backdrop_path'); if (isset($backdrop) && sizeof($backdrop) > 0) { $ret['backdrop'] = "http://image.tmdb.org/t/p/original" . $backdrop; } if ($this->echooutput) { $this->pdo->log->doEcho($this->pdo->log->primaryOver("TMDb Found ") . $this->pdo->log->headerOver($ret['title']), true); } return $ret; } /** * @param $imdbId * * @return array|bool */ protected function fetchIMDBProperties($imdbId) { $imdb_regex = [ 'title' => '/(.*?)\s?\(.*?<\/title>/i', 'tagline' => '/taglines:<\/h4>\s([^<]+)/i', 'plot' => '/<p itemprop="description">\s*?(.*?)\s*?<\/p>/i', 'rating' => '/"ratingValue">([\d.]+)<\/span>/i', 'year' => '/<title>.*?\(.*?(\d{4}).*?<\/title>/i', 'cover' => '/<link rel=\'image_src\' href="(http:\/\/ia\.media-imdb\.com.+\.jpg)">/' ]; $imdb_regex_multi = [ 'genre' => '/href="\/genre\/(.*?)\?/i', 'language' => '/<a href="\/language\/.+?\'url\'>(.+?)<\/a>/s', 'type' => '/<meta property=\'og\:type\' content=\"(.+)\" \/>/i' ]; $buffer = Utility::getUrl([ 'url' => 'http://' . ($this->imdburl === false ? 'www' : 'akas') . '.imdb.com/title/tt' . $imdbId . '/', 'Accept-Language' => (($this->pdo->getSetting('lookuplanguage') != '') ? $this->pdo->getSetting('lookuplanguage') : 'en'), 'useragent' => 'Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) ' . 'Version/4.0.4 Mobile/7B334b Safari/531.21.102011-10-16 20:23:10', 'foo=bar' ] ); if ($buffer !== false) { $ret = []; foreach ($imdb_regex as $field => $regex) { if (preg_match($regex, $buffer, $matches)) { $match = $matches[1]; $match1 = strip_tags(trim(rtrim($match))); $ret[$field] = $match1; } } $matches = []; foreach ($imdb_regex_multi as $field => $regex) { if (preg_match_all($regex, $buffer, $matches)) { $match2 = $matches[1]; $match3 = array_map("trim", $match2); $ret[$field] = $match3; } } if ($this->currentTitle !== '' && isset($ret['title'])) { // Check the similarity. similar_text($this->currentTitle, $ret['title'], $percent); if ($percent < 40) { if ($this->debug) { $this->debugging->log( get_class(), __FUNCTION__, 'Found (' . $ret['title'] . ') from IMDB, but it\'s only ' . $percent . '% similar to (' . $this->currentTitle . ')', \Logger::LOG_INFO ); } return false; } } // Actors. if (preg_match('/<table class="cast_list">(.+?)<\/table>/s', $buffer, $hit)) { if (preg_match_all('/<span class="itemprop" itemprop="name">\s*(.+?)\s*<\/span>/i', $hit[0], $results, PREG_PATTERN_ORDER)) { $ret['actors'] = $results[1]; } } // Directors. if (preg_match('/itemprop="directors?".+?<\/div>/s', $buffer, $hit)) { if (preg_match_all('/"name">(.*?)<\/span>/is', $hit[0], $results, PREG_PATTERN_ORDER)) { $ret['director'] = $results[1]; } } if ($this->echooutput && isset($ret['title'])) { $this->pdo->log->doEcho($this->pdo->log->headerOver("IMDb Found ") . $this->pdo->log->primaryOver($ret['title']), true); } return $ret; } return false; } /** * Fetch TraktTV backdrop / cover / title. * * @param $imdbId * * @return bool|array */ protected function fetchTraktTVProperties($imdbId) { if (is_null($this->traktTv)) { $this->traktTv = new TraktTv(['Settings' => $this->pdo]); } $resp = $this->traktTv->movieSummary('tt' . $imdbId, 'full,images'); if ($resp !== false) { $ret = []; if ($this->checkVariable($resp['images']['poster']['thumb'])) { $ret['cover'] = $resp['images']['poster']['thumb']; } if ($this->checkVariable($resp['images']['banner']['full'])) { $ret['banner'] = $resp['images']['banner']['full']; } if (isset($resp['ids']['trakt'])) { $ret['id'] = $resp['ids']['trakt']; } if (isset($resp['title'])) { $ret['title'] = $resp['title']; } else { return false; } if ($this->echooutput) { $this->pdo->log->doEcho($this->pdo->log->alternateOver("Trakt Found ") . $this->pdo->log->headerOver($ret['title']), true); } return $ret; } return false; } /** * Update a release with a IMDB id. * * @param string $buffer Data to parse a IMDB id/Trakt Id from. * @param string $service Method that called this method. * @param int $id id of the release. * @param int $processImdb To get IMDB info on this IMDB id or not. * * @return string */ public function doMovieUpdate($buffer, $service, $id, $processImdb = 1) { $imdbID = false; if (is_string($buffer) && preg_match('/(?:imdb.*?)?(?:tt|Title\?)(?P<imdbid>\d{5,7})/i', $buffer, $matches)) { $imdbID = $matches['imdbid']; } $traktID = false; if (is_string($buffer) && preg_match('/(?:trakt.*?)/i', $buffer, $matches)) { $traktID = $matches['trakt']; } if ($traktID !== false){ if ($this->echooutput && $this->service !== '') { $this->pdo->log->doEcho($this->pdo->log->headerOver($service . ' found Trakt id: ') . $this->pdo->log->primary($traktID)); } $this->pdo->queryExec(sprintf('UPDATE releases SET traktid = %d WHERE id = %d', $this->pdo->escapeString($traktID), $id)); } if ($imdbID !== false) { $this->service = $service; if ($this->echooutput && $this->service !== '') { $this->pdo->log->doEcho($this->pdo->log->headerOver($service . ' found IMDBid: ') . $this->pdo->log->primary('tt' . $imdbID)); } $this->pdo->queryExec(sprintf('UPDATE releases SET imdbid = %s WHERE id = %d', $this->pdo->escapeString($imdbID), $id)); // If set, scan for imdb info. if ($processImdb == 1) { $movCheck = $this->getMovieInfo($imdbID); if ($movCheck === false || (isset($movCheck['updateddate']) && (time() - strtotime($movCheck['updateddate'])) > 2592000)) { if ($this->updateMovieInfo($imdbID) === false) { $this->pdo->queryExec(sprintf('UPDATE releases SET imdbid = %s WHERE id = %d', 0000000, $id)); } } } } return $imdbID; } /** * Process releases with no IMDB id's. * * @param string $groupID (Optional) id of a group to work on. * @param string $guidChar (Optional) First letter of a release GUID to use to get work. * @param int $lookupIMDB (Optional) 0 Don't lookup IMDB, 1 lookup IMDB, 2 lookup IMDB on releases that were renamed. */ public function processMovieReleases($groupID = '', $guidChar = '', $lookupIMDB = 1) { if ($lookupIMDB == 0) { return; } // Get all releases without an IMDB id. $res = $this->pdo->query( sprintf(" SELECT r.searchname, r.id FROM releases r WHERE r.imdbid IS NULL AND r.nzbstatus = 1 AND r.categoryid BETWEEN 2000 AND 2999 %s %s %s LIMIT %d", ($groupID === '' ? '' : ('AND r.groupid = ' . $groupID)), ($guidChar === '' ? '' : ('AND r.guid ' . $this->pdo->likeString($guidChar, false, true))), ($lookupIMDB == 2 ? 'AND r.isrenamed = 1' : ''), $this->movieqty ) ); $movieCount = count($res); if ($movieCount > 0) { if (is_null($this->traktTv)) { $this->traktTv = new TraktTv(['Settings' => $this->pdo]); } if ($this->echooutput && $movieCount > 1) { $this->pdo->log->doEcho($this->pdo->log->header("Processing " . $movieCount . " movie releases.")); } // Loop over releases. foreach ($res as $arr) { // Try to get a name/year. if ($this->parseMovieSearchName($arr['searchname']) === false) { //We didn't find a name, so set to all 0's so we don't parse again. $this->pdo->queryExec(sprintf("UPDATE releases SET imdbid = 0000000 WHERE id = %d", $arr["id"])); continue; } else { $this->currentRelID = $arr['id']; $movieName = $this->currentTitle; if ($this->currentYear !== false) { $movieName .= ' (' . $this->currentYear . ')'; } if ($this->echooutput) { $this->pdo->log->doEcho($this->pdo->log->primaryOver("Looking up: ") . $this->pdo->log->headerOver($movieName), true); } // Check local DB. $getIMDBid = $this->localIMDBsearch(); if ($getIMDBid !== false) { $imdbID = $this->doMovieUpdate('tt' . $getIMDBid, 'Local DB', $arr['id']); if ($imdbID !== false) { continue; } } // Check OMDB api. $buffer = Utility::getUrl([ 'url' => 'http://www.omdbapi.com/?t=' . urlencode($this->currentTitle) . ($this->currentYear !== false ? ('&y=' . $this->currentYear) : '') . '&r=json' ] ); if ($buffer !== false) { $getIMDBid = json_decode($buffer); if (isset($getIMDBid->imdbid)) { $imdbID = $this->doMovieUpdate($getIMDBid->imdbid, 'OMDbAPI', $arr['id']); if ($imdbID !== false) { continue; } } } // Check on trakt. $data = $this->traktTv->movieSummary($movieName, 'full,images'); if ($data !== false) { $this->parseTraktTv($data); if (isset($data['ids']['imdb'])) { $imdbID = $this->doMovieUpdate($data['ids']['imdb'], 'Trakt', $arr['id']); if ($imdbID !== false) { continue; } } if (isset($data['ids']['trakt'])) { $traktID = $this->doMovieUpdate($data['ids']['trakt'], 'Trakt', $arr['id']); if ($traktID !== false) { continue; } } } // Try on search engines. if ($this->searchEngines && $this->currentYear !== false) { if ($this->imdbIDFromEngines() === true) { continue; } } // We failed to get an IMDB id from all sources. $this->pdo->queryExec(sprintf("UPDATE releases SET imdbid = 0000000 WHERE id = %d", $arr["id"])); } } } } /** * Try to fetch an IMDB id locally. * * @return int|bool Int, the imdbid when true, Bool when false. */ protected function localIMDBsearch() { $query = 'SELECT imdbid FROM movieinfo'; $andYearIn = ''; //If we found a year, try looking in a 4 year range. if ($this->currentYear !== false) { $start = (int) $this->currentYear - 2; $end = (int) $this->currentYear + 2; $andYearIn = 'AND year IN ('; while ($start < $end) { $andYearIn .= $start . ','; $start++; } $andYearIn .= $end . ')'; } $IMDBCheck = $this->pdo->queryOneRow( sprintf('%s WHERE title %s %s', $query, $this->pdo->likeString($this->currentTitle), $andYearIn)); // Look by %word%word%word% etc.. if ($IMDBCheck === false) { $pieces = explode(' ', $this->currentTitle); $tempTitle = '%'; foreach ($pieces as $piece) { $tempTitle .= str_replace(["'", "!", '"'], '', $piece) . '%'; } $IMDBCheck = $this->pdo->queryOneRow( sprintf("%s WHERE replace(replace(title, \"'\", ''), '!', '') %s %s", $query, $this->pdo->likeString($tempTitle), $andYearIn ) ); } // Try replacing er with re ? if ($IMDBCheck === false) { $tempTitle = str_replace('er', 're', $this->currentTitle); if ($tempTitle !== $this->currentTitle) { $IMDBCheck = $this->pdo->queryOneRow( sprintf('%s WHERE title %s %s', $query, $this->pdo->likeString($tempTitle), $andYearIn ) ); // Final check if everything else failed. if ($IMDBCheck === false) { $pieces = explode(' ', $tempTitle); $tempTitle = '%'; foreach ($pieces as $piece) { $tempTitle .= str_replace(["'", "!", '"'], "", $piece) . '%'; } $IMDBCheck = $this->pdo->queryOneRow( sprintf("%s WHERE replace(replace(replace(title, \"'\", ''), '!', ''), '\"', '') %s %s", $query, $this->pdo->likeString($tempTitle), $andYearIn ) ); } } } return ( ($IMDBCheck === false ? false : (is_numeric($IMDBCheck['imdbid']) ? (int)$IMDBCheck['imdbid'] : false ) ) ); } /** * Try to get an IMDB id from search engines. * * @return bool */ protected function imdbIDFromEngines() { if ($this->googleLimit < 41 && (time() - $this->googleBan) > 600) { if ($this->googleSearch() === true) { return true; } } if ($this->yahooLimit < 41) { if ($this->yahooSearch() === true) { return true; } } // Not using this right now because bing's advanced search is not good enough. /*if ($this->bingLimit < 41) { if ($this->bingSearch() === true) { return true; } }*/ return false; } /** * Try to find a IMDB id on google.com * * @return bool */ protected function googleSearch() { $buffer =Utility::getUrl([ 'url' => 'https://www.google.com/search?hl=en&as_q=&as_epq=' . urlencode( $this->currentTitle . ' ' . $this->currentYear ) . '&as_oq=&as_eq=&as_nlo=&as_nhi=&lr=&cr=&as_qdr=all&as_sitesearch=' . urlencode('www.imdb.com/title/') . '&as_occt=title&safe=images&tbs=&as_filetype=&as_rights=' ] ); // Make sure we got some data. if ($buffer !== false) { $this->googleLimit++; if (preg_match('/(To continue, please type the characters below)|(- did not match any documents\.)/i', $buffer, $matches)) { if (!empty($matches[1])) { $this->googleBan = time(); } } else if ($this->doMovieUpdate($buffer, 'Google.com', $this->currentRelID) !== false) { return true; } } return false; } /** * Try to find a IMDB id on bing.com * * @return bool */ protected function bingSearch() { $buffer = Utility::getUrl([ 'url' => "http://www.bing.com/search?q=" . urlencode( '("' . $this->currentTitle . '" and "' . $this->currentYear . '") site:www.imdb.com/title/' ) . '&qs=n&form=QBLH&filt=all' ] ); if ($buffer !== false) { $this->bingLimit++; if ($this->doMovieUpdate($buffer, 'Bing.com', $this->currentRelID) !== false) { return true; } } return false; } /** * Try to find a IMDB id on yahoo.com * * @return bool */ protected function yahooSearch() { $buffer = Utility::getUrl([ 'url' => "http://search.yahoo.com/search?n=10&ei=UTF-8&va_vt=title&vo_vt=any&ve_vt=any&vp_vt=any&vf=all&vm=p&fl=0&fr=fp-top&p=intitle:" . urlencode( 'intitle:' . implode(' intitle:', explode( ' ', preg_replace( '/\s+/', ' ', preg_replace( '/\W/', ' ', $this->currentTitle ) ) ) ) . ' intitle:' . $this->currentYear ) . '&vs=' . urlencode('www.imdb.com/title/') ] ); if ($buffer !== false) { $this->yahooLimit++; if ($this->doMovieUpdate($buffer, 'Yahoo.com', $this->currentRelID) !== false) { return true; } } return false; } /** * Parse a movie name from a release search name. * * @param string $releaseName * * @return bool */ protected function parseMovieSearchName($releaseName) { $name = $year = ''; $followingList = '[^\w]((1080|480|720)p|AC3D|Directors([^\w]CUT)?|DD5\.1|(DVD|BD|BR)(Rip)?|BluRay|divx|HDTV|iNTERNAL|LiMiTED|(Real\.)?Proper|RE(pack|Rip)|Sub\.?(fix|pack)|Unrated|WEB-DL|(x|H)[-._ ]?264|xvid)[^\w]'; /* Initial scan of getting a year/name. * [\w. -]+ Gets 0-9a-z. - characters, most scene movie titles contain these chars. * ie: [61420]-[FULL]-[a.b.foreignEFNet]-[ Coraline.2009.DUTCH.INTERNAL.1080p.BluRay.x264-VeDeTT ]-[21/85] - "vedett-coralien-1080p.r04" yEnc * Then we look up the year, (19|20)\d\d, so $matches[1] would be Coraline $matches[2] 2009 */ if (preg_match('/(?P<name>[\w. -]+)[^\w](?P<year>(19|20)\d\d)/i', $releaseName, $matches)) { $name = $matches['name']; $year = $matches['year']; /* If we didn't find a year, try to get a name anyways. * Try to look for a title before the $followingList and after anything but a-z0-9 two times or more (-[ for example) */ } else if (preg_match('/([^\w]{2,})?(?P<name>[\w .-]+?)' . $followingList . '/i', $releaseName, $matches)) { $name = $matches['name']; } // Check if we got something. if ($name !== '') { // If we still have any of the words in $followingList, remove them. $name = preg_replace('/' . $followingList . '/i', ' ', $name); // Remove periods, underscored, anything between parenthesis. $name = preg_replace('/\(.*?\)|[._]/i', ' ', $name); // Finally remove multiple spaces and trim leading spaces. $name = trim(preg_replace('/\s{2,}/', ' ', $name)); // Check if the name is long enough and not just numbers. if (strlen($name) > 4 && !preg_match('/^\d+$/', $name)) { if ($this->debug && $this->echooutput) { $this->pdo->log->doEcho("DB name: {$releaseName}", true); } $this->currentTitle = $name; $this->currentYear = ($year === '' ? false : $year); return true; } } return false; } /** * Get upcoming movies. * * @param $type * @param string $source * * @return array|bool */ public function getUpcoming($type, $source = 'rottentomato') { $list = $this->pdo->queryOneRow( sprintf('SELECT * FROM upcoming WHERE source = %s AND typeid = %d', $this->pdo->escapeString($source), $type) ); if ($list === false) { $this->updateUpcoming(); $list = $this->pdo->queryOneRow( sprintf('SELECT * FROM upcoming WHERE source = %s AND typeid = %d', $this->pdo->escapeString($source), $type) ); } return $list; } /** * Update upcoming movies. */ public function updateUpcoming() { if ($this->echooutput) { $this->pdo->log->doEcho($this->pdo->log->header('Updating movie schedule using rotten tomatoes.')); } $rt = new RottenTomato($this->pdo->getSetting('rottentomatokey')); if ($rt instanceof RottenTomato) { $this->_getRTData('boxoffice', $rt); $this->_getRTData('theaters', $rt); $this->_getRTData('opening', $rt); $this->_getRTData('upcoming', $rt); $this->_getRTData('dvd', $rt); if ($this->echooutput) { $this->pdo->log->doEcho($this->pdo->log->header("Updated successfully.")); } } else if ($this->echooutput) { $this->pdo->log->doEcho($this->pdo->log->header("Error retrieving your RottenTomato API Key. Exiting..." . PHP_EOL)); } } protected function _getRTData($operation = '', $rt) { $count = 0; $check = false; do { $count++; switch ($operation) { case 'boxoffice': $data = $rt->getBoxOffice(); $update = Movie::SRC_BOXOFFICE; break; case 'theaters': $data = $rt->getInTheaters(); $update = Movie::SRC_INTHEATRE; break; case 'opening': $data = $rt->getOpening(); $update = Movie::SRC_OPENING; break; case 'upcoming': $data = $rt->getUpcoming(); $update = Movie::SRC_UPCOMING; break; case 'dvd': $data = $rt->getDVDReleases(); $update = Movie::SRC_DVD; break; default: $data = false; $update = 0; } if ($data !== false && $data !== '') { if (isset($data)) { $count = 2; $check = true; } } } while ($count < 2); if ($check === true) { $success = $this->updateInsUpcoming('rottentomato', $update, $data); if ($this->echooutput) { if ($success !== false) { $this->pdo->log->doEcho($this->pdo->log->header(sprintf("Added/updated movies to the %s list.", $operation))); } else { $this->pdo->log->doEcho($this->pdo->log->primary(sprintf("No new updates for %s list.", $operation))); } } } else { exit(PHP_EOL . $this->pdo->log->error("Unable to fetch from Rotten Tomatoes, verify your API Key." . PHP_EOL)); } } /** * Update upcoming table. * * @param string $source * @param $type * @param string|false $info * * @return boolean|int */ protected function updateInsUpcoming($source, $type, $info) { return $this->pdo->queryExec( sprintf(" INSERT INTO upcoming (source, typeid, info, updateddate) VALUES (%s, %d, %s, NOW()) ON DUPLICATE KEY UPDATE info = %s", $this->pdo->escapeString($source), $type, $this->pdo->escapeString($info), $this->pdo->escapeString($info) ) ); } /** * Get IMDB genres. * * @return array */ public function getGenres() { return [ 'Action', 'Adventure', 'Animation', 'Biography', 'Comedy', 'Crime', 'Documentary', 'Drama', 'Family', 'Fantasy', 'Film-Noir', 'Game-Show', 'History', 'Horror', 'Music', 'Musical', 'Mystery', 'News', 'Reality-TV', 'Romance', 'Sci-Fi', 'Sport', 'Talk-Show', 'Thriller', 'War', 'Western' ]; } }