false, 'Settings' => null, ]; $options += $defaults; $s = new Sites(); $this->site = $s->get(); $this->echo = ($options['Echo'] && NN_ECHOCLI); $this->pdo = ($options['Settings'] instanceof DB ? $options['Settings'] : new DB()); $s = new Sites(); $this->site = $s->get(); $this->nzbs = ($this->site->maxnfoprocessed != '') ? (int)$this->site->maxnfoprocessed : 100; $this->maxsize = ($this->site->maxsizetoprocessnfo != '') ? (int)$this->site->maxsizetoprocessnfo : 100; $this->maxsize = ($this->maxsize > 0 ? ('AND size < ' . ($this->maxsize * 1073741824)) : ''); $this->minsize = ($this->site->minsizetoprocessnfo != '') ? (int)$this->site->minsizetoprocessnfo : 100; $this->minsize = ($this->minsize > 0 ? ('AND size > ' . ($this->minsize * 1048576)) : ''); $this->maxRetries = (int)($this->site->maxnforetries >= 0 ? -((int)$this->site->maxnforetries + 1) : self::NFO_UNPROC); $this->maxRetries = ($this->maxRetries < -8 ? -8 : $this->maxRetries); $this->tmpPath = (string)$this->site->tmpunrarpath; if (!preg_match('/[\/\\\\]$/', $this->tmpPath)) { $this->tmpPath .= DS; } } /** * Look for a TvRage id in a string. * * @param string $str The string with a TvRage id. * @return string The TVRage id on success. * * @return bool False on failure. * * @access public */ public function parseRageId($str) { if (preg_match('/tvrage\.com\/shows\/id-(\d{1,6})/i', $str, $matches)) { return trim($matches[1]); } return false; } /** * Confirm this is an NFO file. * * @param string $possibleNFO The nfo. * @param string $guid The guid of the release. * * @return bool True on success, False on failure. * * @access public */ public function isNFO(&$possibleNFO, $guid) { if ($possibleNFO === false) { return false; } // Make sure it's not too big or small, size needs to be at least 12 bytes for header checking. Ignore common file types. $size = strlen($possibleNFO); if ($size < 65535 && $size > 11 && !preg_match( '/\A(\s*<\?xml|=newz\[NZB\]=|RIFF|\s*[RP]AR|.{0,10}(JFIF|matroska|ftyp|ID3))|;\s*Generated\s*by.*SF\w/i' , $possibleNFO)) { // File/GetId3 work with files, so save to disk. $tmpPath = $this->tmpPath . $guid . '.nfo'; file_put_contents($tmpPath, $possibleNFO); // Linux boxes have 'file' (so should Macs), Windows *can* have it too: see GNUWIN.txt in docs. $result = Utility::fileInfo($tmpPath); if (!empty($result)) { // Check if it's text. if (preg_match('/(ASCII|ISO-8859|UTF-(8|16|32).*?)\s*text/', $result)) { @unlink($tmpPath); return true; // Or binary. } else if (preg_match('/^(JPE?G|Parity|PNG|RAR|XML|(7-)?[Zz]ip)/', $result) || preg_match('/[\x00-\x08\x12-\x1F\x0B\x0E\x0F]/', $possibleNFO)) { @unlink($tmpPath); return false; } } // If above checks couldn't make a categorical identification, Use GetId3 to check if it's an image/video/rar/zip etc.. $getid3 = new getid3(); $check = $getid3->analyze($tmpPath); @unlink($tmpPath); if (isset($check['error'])) { // Check if it's a par2. $par2info = new Par2Info(); $par2info->setData($possibleNFO); if ($par2info->error) { // Check if it's an SFV. $sfv = new SfvInfo(); $sfv->setData($possibleNFO); if ($sfv->error) { return true; } } } } return false; } /** * Add an NFO from alternate sources. ex.: PreDB, rar, zip, etc... * * @param string $nfo The nfo. * @param array $release The SQL row for this release. * @param object $nntp Instance of class NNTP. * * @return bool True on success, False on failure. * * @access public */ public function addAlternateNfo(&$nfo, $release, $nntp) { if ($release['id'] > 0 && $this->isNFO($nfo, $release['guid'])) { $check = $this->pdo->queryOneRow(sprintf('SELECT id FROM releasenfo WHERE releaseid = %d', $release['id'])); if ($check === false) { $this->pdo->queryInsert( sprintf('INSERT INTO releasenfo (nfo, releaseid) VALUES (compress(%s), %d)', $this->pdo->escapeString($nfo), $release['id'] ) ); } $this->pdo->queryExec(sprintf('UPDATE releases SET nfostatus = %d WHERE id = %d', self::NFO_FOUND, $release['id'])); if (!isset($release['completion'])) { $release['completion'] = 0; } if ($release['completion'] == 0) { $nzbContents = new NZBContents( [ 'Echo' => $this->echo, 'NNTP' => $nntp, 'Nfo' => $this, 'Settings' => $this->pdo, 'PostProcess' => new PProcess(['Echo' => $this->echo, 'Settings' => $this->pdo, 'Nfo' => $this]) ] ); $nzbContents->parseNZB($release['guid'], $release['id'], $release['groupid']); } return true; } return false; } /** * Get a string like this: * "AND r.nzbstatus = 1 AND r.nfostatus BETWEEN -8 AND -1 AND r.size < 1073741824 AND r.size > 1048576" * To use in a query. * * @param DB $pdo * * @return string * @access public * @static */ static public function NfoQueryString(DB & $pdo) { $s = new Sites(); $site = $s->get(); $maxSize = $site->maxsizetoprocessnfo; $minSize = $site->minsizetoprocessnfo; $maxRetries = (int)($site->maxnforetries >= 0 ? -((int)$site->maxnforetries + 1) : self::NFO_UNPROC); return ( sprintf( 'AND r.nzbstatus = %d AND r.nfostatus BETWEEN %d AND %d %s %s', NZB::NZB_ADDED, ($maxRetries < -8 ? -8 : $maxRetries), self::NFO_UNPROC, (($maxSize != '' && $maxSize > 0) ? ('AND r.size < ' . ($maxSize * 1073741824)) : ''), (($minSize != '' && $minSize > 0) ? ('AND r.size > ' . ($minSize * 1048576)) : '') ) ); } /** * Attempt to find NFO files inside the NZB's of releases. * * @param object $nntp Instance of class NNTP. * @param string $groupID (optional) Group id. * @param string $guidChar (optional) First character of the release GUID (used for multi-processing). * @param int $processImdb (optional) Attempt to find IMDB id's in the NZB? * @param int $processTvrage (optional) Attempt to find TvRage id's in the NZB? * * @return int How many NFO's were processed? * * @access public */ public function processNfoFiles($nntp, $groupID = '', $guidChar = '', $processImdb = 1, $processTvrage = 1) { $ret = 0; $guidCharQuery = ($guidChar === '' ? '' : 'AND r.guid ' . $this->pdo->likeString($guidChar, false, true)); $groupIDQuery = ($groupID === '' ? '' : 'AND r.groupid = ' . $groupID); $optionsQuery = self::NfoQueryString($this->pdo); $res = $this->pdo->query( sprintf(' SELECT r.id, r.guid, r.groupid, r.name FROM releases r WHERE 1=1 %s %s %s ORDER BY r.nfostatus ASC, r.postdate DESC LIMIT %d', $optionsQuery, $guidCharQuery, $groupIDQuery, $this->nzbs ) ); $nfoCount = count($res); if ($nfoCount > 0) { $this->pdo->log->doEcho( $this->pdo->log->primary( PHP_EOL . ($guidChar === '' ? '' : '[' . $guidChar . '] ') . ($groupID === '' ? '' : '[' . $groupID . '] ') . 'Processing ' . $nfoCount . ' NFO(s), starting at ' . $this->nzbs . ' * = hidden NFO, + = NFO, - = no NFO, f = download failed.' ) ); if ($this->echo) { // Get count of releases per nfo status $nfoStats = $this->pdo->queryDirect( sprintf(' SELECT r.nfostatus AS status, COUNT(*) AS count FROM releases r WHERE 1=1 %s %s %s GROUP BY r.nfostatus ORDER BY r.nfostatus ASC', $optionsQuery, $guidCharQuery, $groupIDQuery ) ); if ($nfoStats instanceof Traversable) { $outString = PHP_EOL . 'Available to process'; foreach ($nfoStats as $row) { $outString .= ', ' . $row['status'] . ' = ' . number_format($row['count']); } $this->pdo->log->doEcho($this->pdo->log->header($outString . '.')); } } $groups = new Groups(['Settings' => $this->pdo]); $nzbContents = new NZBContents( [ 'Echo' => $this->echo, 'NNTP' => $nntp, 'Nfo' => $this, 'Settings' => $this->pdo, 'PostProcess' => new PProcess(['Echo' => $this->echo, 'Nfo' => $this, 'Settings' => $this->pdo]) ] ); $movie = new Film(['Echo' => $this->echo, 'Settings' => $this->pdo]); $tvRage = new TvAnger(['Echo' => $this->echo, 'Settings' => $this->pdo]); foreach ($res as $arr) { $fetchedBinary = $nzbContents->getNFOfromNZB($arr['guid'], $arr['id'], $arr['groupid'], $groups->getByNameByID($arr['groupid'])); if ($fetchedBinary !== false) { // Insert nfo into database. $cp = 'COMPRESS(%s)'; $nc = $this->pdo->escapeString($fetchedBinary); $ckreleaseid = $this->pdo->queryOneRow(sprintf('SELECT id FROM releasenfo WHERE releaseid = %d', $arr['id'])); if (!isset($ckreleaseid['id'])) { $this->pdo->queryInsert(sprintf('INSERT INTO releasenfo (nfo, releaseid) VALUES (' . $cp . ', %d)', $nc, $arr['id'])); } $this->pdo->queryExec(sprintf('UPDATE releases SET nfostatus = %d WHERE id = %d', self::NFO_FOUND, $arr['id'])); $ret++; $movie->doMovieUpdate($fetchedBinary, 'nfo', $arr['id'], $processImdb); // If set scan for tvrage info. if ($processTvrage == 1) { $rageId = $this->parseRageId($fetchedBinary); if ($rageId !== false) { $show = $tvRage->parseNameEpSeason($arr['name']); if (is_array($show) && $show['name'] != '') { // Update release with season, ep, and air date info (if available) from release title. $tvRage->updateEpInfo($show, $arr['id']); $rid = $tvRage->getByRageID($rageId); if (!$rid) { $tvrShow = $tvRage->getRageInfoFromService($rageId); $tvRage->updateRageInfo($rageId, $show, $tvrShow, $arr['id']); } } } } } } } // Remove nfo that we cant fetch after 5 attempts. $releases = $this->pdo->queryDirect( sprintf( 'SELECT r.id FROM releases r WHERE r.nzbstatus = %d AND r.nfostatus < %d %s %s', NZB::NZB_ADDED, $this->maxRetries, $groupIDQuery, $guidCharQuery ) ); if ($releases instanceof Traversable) { foreach ($releases as $release) { $this->pdo->queryExec( sprintf('DELETE FROM releasenfo WHERE nfo IS NULL AND releaseid = %d', $release['id']) ); } } // Set releases with no NFO. $this->pdo->queryExec( sprintf(' UPDATE releases r SET r.nfostatus = %d WHERE r.nzbstatus = %d AND r.nfostatus < %d %s %s', self::NFO_FAILED, NZB::NZB_ADDED, $this->maxRetries, $groupIDQuery, $guidCharQuery ) ); if ($this->echo) { if ($nfoCount > 0) { echo PHP_EOL; } if ($ret > 0) { $this->pdo->log->doEcho($ret . ' NFO file(s) found/processed.', true); } } return $ret; } }