. * * @author ruhllatio * @copyright 2016 nZEDb */ namespace App\Http\Controllers\Api; use App\Models\Category; use App\Models\Release; use Illuminate\Support\Carbon; /** * Class XMLReturn. */ class XML_Response { /** * @var string The buffered cData before final write */ protected string $cdata; /** * The RSS namespace used for the output. */ protected string $namespace; /** * The trailing URL parameters on the request. */ protected mixed $parameters; /** * The release we are adding to the stream. */ protected mixed $release; /** * The retrieved releases we are returning from the API call. */ protected mixed $releases; /** * The various server variables and active categories. */ protected mixed $server; /** * The XML formatting operation we are returning. */ protected mixed $type; /** * The XMLWriter Class. */ protected \XMLWriter $xml; protected mixed $offset; /** * XMLReturn constructor. */ public function __construct(array $options = []) { $defaults = [ 'Parameters' => null, 'Data' => null, 'Server' => null, 'Offset' => null, 'Type' => null, ]; $options += $defaults; $this->parameters = $options['Parameters']; $this->releases = $options['Data']; $this->server = $options['Server']; $this->offset = $options['Offset']; $this->type = $options['Type']; $this->xml = new \XMLWriter; $this->xml->openMemory(); $this->xml->setIndent(true); } public function returnXML(): bool|string { if ($this->xml) { switch ($this->type) { case 'caps': return $this->returnCaps(); break; case 'api': $this->namespace = 'newznab'; return $this->returnApiXml(); break; case 'rss': $this->namespace = 'nntmux'; return $this->returnApiRssXml(); break; case 'reg': return $this->returnReg(); break; } } return false; } /** * XML writes and returns the API capabilities. * * @return string The XML Formatted string data */ protected function returnCaps(): string { $this->xml->startDocument('1.0', 'UTF-8'); $this->xml->startElement('caps'); $this->addNode(['name' => 'server', 'data' => $this->server['server']]); $this->addNode(['name' => 'limits', 'data' => $this->server['limits']]); $this->addNode(['name' => 'registration', 'data' => $this->server['registration']]); $this->addNodes(['name' => 'searching', 'data' => $this->server['searching']]); $this->writeCategoryListing(); $this->xml->endElement(); $this->xml->endDocument(); return $this->xml->outputMemory(); } /** * XML writes and returns the API data. * * @return string The XML Formatted string data */ protected function returnApiRssXml(): string { $this->xml->startDocument('1.0', 'UTF-8'); $this->includeRssAtom(); // Open RSS $this->xml->startElement('channel'); // Open channel $this->includeRssAtomLink(); $this->includeMetaInfo(); $this->includeImage(); $this->includeTotalRows(); $this->includeLimits(); $this->includeReleases(); $this->xml->endElement(); // End channel $this->xml->endElement(); // End RSS $this->xml->endDocument(); return $this->xml->outputMemory(); } /** * XML writes and returns the API data. * * @return string The XML Formatted string data */ protected function returnApiXml(): string { $this->xml->startDocument('1.0', 'UTF-8'); $this->includeRssAtom(); // Open RSS $this->xml->startElement('channel'); // Open channel $this->includeMetaInfo(); $this->includeImage(); $this->includeTotalRows(); $this->includeLimits(); $this->includeReleases(); $this->xml->endElement(); // End channel $this->xml->endElement(); // End RSS $this->xml->endDocument(); return $this->xml->outputMemory(); } /** * @return string The XML formatted registration information */ protected function returnReg(): string { $this->xml->startDocument('1.0', 'UTF-8'); $this->xml->startElement('register'); $this->xml->writeAttribute('username', $this->parameters['username']); $this->xml->writeAttribute('password', $this->parameters['password']); $this->xml->writeAttribute('apikey', $this->parameters['token']); $this->xml->endElement(); $this->xml->endDocument(); return $this->xml->outputMemory(); } /** * Starts a new element, loops through the attribute data and ends the element. * * @param array $element An array with the name of the element and the attribute data */ protected function addNode(array $element): void { $this->xml->startElement($element['name']); foreach ($element['data'] as $attr => $val) { $this->xml->writeAttribute($attr, $val); } $this->xml->endElement(); } /** * Starts a new element, loops through the attribute data and ends the element. * * @param array $element An array with the name of the element and the attribute data */ protected function addNodes(array $element): void { $this->xml->startElement($element['name']); foreach ($element['data'] as $elem => $value) { $subelement['name'] = $elem; $subelement['data'] = $value; $this->addNode($subelement); } $this->xml->endElement(); } /** * Adds the site category listing to the XML feed. */ protected function writeCategoryListing(): void { $this->xml->startElement('categories'); foreach ($this->server['categories'] as $this->parameters) { $this->xml->startElement('category'); $this->xml->writeAttribute('id', $this->parameters['id']); $this->xml->writeAttribute('name', html_entity_decode($this->parameters['title'])); if (! empty($this->parameters['description'])) { $this->xml->writeAttribute('description', html_entity_decode($this->parameters['description'])); } foreach ($this->parameters['categories'] as $c) { $this->xml->startElement('subcat'); $this->xml->writeAttribute('id', $c['id']); $this->xml->writeAttribute('name', html_entity_decode($c['title'])); if (! empty($c['description'])) { $this->xml->writeAttribute('description', html_entity_decode($c['description'])); } $this->xml->endElement(); } $this->xml->endElement(); } } /** * Adds RSS Atom information to the XML. */ protected function includeRssAtom(): void { $url = match ($this->namespace) { 'newznab' => 'http://www.newznab.com/DTD/2010/feeds/attributes/', default => $this->server['server']['url'].'/rss-info/', }; $this->xml->startElement('rss'); $this->xml->writeAttribute('version', '2.0'); $this->xml->writeAttribute('xmlns:atom', 'http://www.w3.org/2005/Atom'); $this->xml->writeAttribute("xmlns:{$this->namespace}", $url); $this->xml->writeAttribute('encoding', 'utf-8'); } protected function includeRssAtomLink(): void { $this->xml->startElement('atom:link'); $this->xml->startAttribute('href'); $this->xml->text($this->server['server']['url'].($this->namespace === 'newznab' ? '/api/v1/api' : '/rss')); $this->xml->endAttribute(); $this->xml->startAttribute('rel'); $this->xml->text('self'); $this->xml->endAttribute(); $this->xml->startAttribute('type'); $this->xml->text('application/rss+xml'); $this->xml->endAttribute(); $this->xml->endElement(); } /** * Writes the channel information for the feed. */ protected function includeMetaInfo(): void { $server = $this->server['server']; switch ($this->namespace) { case 'newznab': $path = '/apihelp/'; $tag = 'API'; break; case 'nntmux': default: $path = '/rss-info/'; $tag = 'RSS'; } $this->xml->writeElement('title', $server['title']); $this->xml->writeElement('description', $server['title']." {$tag} Details"); $this->xml->writeElement('link', $server['url']); $this->xml->writeElement('language', 'en-gb'); $this->xml->writeElement('webMaster', $server['email'].' '.$server['title']); $this->xml->writeElement('category', $server['meta']); $this->xml->writeElement('generator', 'nntmux'); $this->xml->writeElement('ttl', '10'); $this->xml->writeElement('docs', $this->server['server']['url'].$path); } /** * Adds nntmux logo data to the XML. */ protected function includeImage(): void { $this->xml->startElement('image'); $this->xml->writeAttribute('url', $this->server['server']['url'].'/assets/images/tmux_logo.png'); $this->xml->writeAttribute('title', $this->server['server']['title']); $this->xml->writeAttribute('link', $this->server['server']['url']); $this->xml->writeAttribute( 'description', 'Visit '.$this->server['server']['title'].' - '.$this->server['server']['strapline'] ); $this->xml->endElement(); } public function includeTotalRows(): void { $this->xml->startElement($this->namespace.':response'); $this->xml->writeAttribute('offset', $this->offset); $this->xml->writeAttribute('total', $this->releases[0]->_totalrows ?? 0); $this->xml->endElement(); } public function includeLimits(): void { $this->xml->startElement($this->namespace.':apilimits'); $this->xml->writeAttribute('apicurrent', $this->parameters['requests']); $this->xml->writeAttribute('apimax', $this->parameters['apilimit']); $this->xml->writeAttribute('grabcurrent', $this->parameters['grabs']); $this->xml->writeAttribute('grabmax', $this->parameters['downloadlimit']); if (! empty($this->parameters['oldestapi'])) { $this->xml->writeAttribute('apioldesttime', $this->parameters['oldestapi']); } if (! empty($this->parameters['oldestgrab'])) { $this->xml->writeAttribute('graboldesttime', $this->parameters['oldestgrab']); } $this->xml->endElement(); } /** * Loop through the releases and add their info to the XML stream. */ public function includeReleases(): void { if (! empty($this->releases)) { if (! $this->releases instanceof Release) { foreach ($this->releases as $this->release) { $this->xml->startElement('item'); $this->includeReleaseMain(); $this->setZedAttributes(); $this->xml->endElement(); } } else { $this->release = $this->releases; $this->xml->startElement('item'); $this->includeReleaseMain(); $this->setZedAttributes(); $this->xml->endElement(); } } } /** * Writes the primary release information. */ public function includeReleaseMain(): void { $this->xml->writeElement('title', $this->release->searchname); $this->xml->startElement('guid'); $this->xml->writeAttribute('isPermaLink', 'true'); $this->xml->text("{$this->server['server']['url']}/details/{$this->release->guid}"); $this->xml->endElement(); $this->xml->writeElement( 'link', "{$this->server['server']['url']}/getnzb?id={$this->release->guid}.nzb". "&r={$this->parameters['token']}". ((int) $this->parameters['del'] === 1 ? '&del=1' : '') ); $this->xml->writeElement('comments', "{$this->server['server']['url']}/details/{$this->release->guid}#comments"); $this->xml->writeElement('pubDate', date(DATE_RSS, strtotime($this->release->adddate))); $this->xml->writeElement('category', $this->release->category_name); if ($this->namespace === 'newznab') { $this->xml->writeElement('description', $this->release->searchname); } else { $this->writeRssCdata(); } if (! isset($this->parameters['dl']) || (isset($this->parameters['dl']) && (int) $this->parameters['dl'] === 1)) { $this->xml->startElement('enclosure'); $this->xml->writeAttribute( 'url', "{$this->server['server']['url']}/getnzb?id={$this->release->guid}.nzb". "&r={$this->parameters['token']}". ((int) $this->parameters['del'] === 1 ? '&del=1' : '') ); $this->xml->writeAttribute('length', $this->release->size); $this->xml->writeAttribute('type', 'application/x-nzb'); $this->xml->endElement(); } } /** * Writes the Zed (newznab) specific attributes. */ protected function setZedAttributes(): void { $this->writeZedAttr('category', $this->release->categories_id); $this->writeZedAttr('size', $this->release->size); if (! empty($this->release->coverurl)) { $this->writeZedAttr( 'coverurl', $this->server['server']['url']."/covers/{$this->release->coverurl}" ); } if ((int) $this->parameters['extended'] === 1) { $this->writeZedAttr('files', $this->release->totalpart); if (($this->release->videos_id > 0 || $this->release->tv_episodes_id > 0) && $this->namespace === 'newznab') { $this->setTvAttr(); } if (isset($this->release->imdbid) && $this->release->imdbid > 0) { $this->writeZedAttr('imdb', $this->release->imdbid); } if (isset($this->release->anidbid) && $this->release->anidbid > 0) { $this->writeZedAttr('anidbid', $this->release->anidbid); } if (isset($this->release->predb_id) && $this->release->predb_id > 0) { $this->writeZedAttr('prematch', 1); } if (isset($this->release->nfostatus) && (int) $this->release->nfostatus === 1) { $this->writeZedAttr( 'info', $this->server['server']['url']. "api?t=info&id={$this->release->guid}&r={$this->parameters['token']}" ); } $this->writeZedAttr('grabs', $this->release->grabs); $this->writeZedAttr('comments', $this->release->comments); $this->writeZedAttr('password', $this->release->passwordstatus); $this->writeZedAttr('usenetdate', Carbon::parse($this->release->postdate)->toRssString()); if (! empty($this->release->group_name)) { $this->writeZedAttr('group', $this->release->group_name); } } } /** * Writes the TV Specific attributes. */ protected function setTvAttr(): void { if (! empty($this->release->title)) { $this->writeZedAttr('title', $this->release->title); } if (isset($this->release->series) && $this->release->series > 0) { $this->writeZedAttr('season', $this->release->series); } if (isset($this->release->episode->episode) && $this->release->episode->episode > 0) { $this->writeZedAttr('episode', $this->release->episode->episode); } if (! empty($this->release->firstaired)) { $this->writeZedAttr('tvairdate', $this->release->firstaired); } if (isset($this->release->tvdb) && $this->release->tvdb > 0) { $this->writeZedAttr('tvdbid', $this->release->tvdb); } if (isset($this->release->trakt) && $this->release->trakt > 0) { $this->writeZedAttr('traktid', $this->release->trakt); } if (isset($this->release->tvrage) && $this->release->tvrage > 0) { $this->writeZedAttr('tvrageid', $this->release->tvrage); $this->writeZedAttr('rageid', $this->release->tvrage); } if (isset($this->release->tvmaze) && $this->release->tvmaze > 0) { $this->writeZedAttr('tvmazeid', $this->release->tvmaze); } if (isset($this->release->imdb) && $this->release->imdb > 0) { $this->writeZedAttr('imdbid', $this->release->imdb); } if (isset($this->release->tmdb) && $this->release->tmdb > 0) { $this->writeZedAttr('tmdbid', $this->release->tmdb); } } /** * Writes individual zed (newznab) type attributes. * * @param string $name The namespaced attribute name tag * @param string|null $value The namespaced attribute value */ protected function writeZedAttr(string $name, ?string $value): void { $this->xml->startElement($this->namespace.':attr'); $this->xml->writeAttribute('name', $name); $this->xml->writeAttribute('value', $value); $this->xml->endElement(); } /** * Writes the cData (HTML format) for the RSS feed * Also calls supplementary cData writes depending upon post process. */ protected function writeRssCdata(): void { $this->cdata = "\n\t