[ 'url' => [ 'name' => 'Repository URL', 'title' => 'Usually ends with /repo/', 'required' => true, 'exampleValue' => 'https://molly.im/fdroid/foss/fdroid/repo' ] ], 'Latest Updates' => [ 'sorting' => [ 'name' => 'Sort By', 'type' => 'list', 'values' => [ 'Latest added apps' => 'added', 'Latest updated apps' => 'lastUpdated' ] ], 'locale' => [ 'name' => 'Locale', 'defaultValue' => 'en-US' ] ], 'Follow Package' => [ 'package' => [ 'name' => 'Package Identifier', 'required' => true, 'exampleValue' => 'im.molly.app' ] ] ]; // Stores repo information private $repo; public function collectData() { if (!extension_loaded('zip')) { throw new \Exception('FDroidRepoBridge requires the php-zip extension'); } $this->repo = $this->fetchData(); switch ($this->queriedContext) { case 'Latest Updates': $this->getAllUpdates(); break; case 'Follow Package': $this->getPackage($this->getInput('package')); break; default: throw new \Exception('Unimplemented Context (collectData)'); } } /** * This method fetches data from arbitrary url and writes to os temp file. * I don't think there's any security problem here but might be DOS problems. */ private function fetchData() { $url = $this->getURI(); $zipFile = getContents($url . '/index-v1.jar'); // On linux this creates a temp file in /tmp/ $temporaryFile = tempnam(sys_get_temp_dir(), 'rssbridge_'); file_put_contents($temporaryFile, $zipFile); $archive = new \ZipArchive(); if ($archive->open($temporaryFile) !== true) { unlink($temporaryFile); throw new \Exception('Failed to extract archive'); } $fp = $archive->getStream('index-v1.json'); if (!$fp) { unlink($temporaryFile); throw new \Exception('Failed to get file pointer'); } $json = stream_get_contents($fp); fclose($fp); $data = Json::decode($json); $archive->close(); unlink($temporaryFile); return $data; } private function getAllUpdates() { $apps = $this->repo['apps']; usort($apps, function ($a, $b) { return $b[$this->getInput('sorting')] <=> $a[$this->getInput('sorting')]; }); $apps = array_slice($apps, 0, self::ITEM_LIMIT); foreach ($apps as $app) { $latest = reset($this->repo['packages'][$app['packageName']]); if (isset($app['localized'])) { // Try provided locale, then en-US, then any $lang = $app['localized']; $lang = $lang[$this->getInput('locale')] ?? $lang['en-US'] ?? reset($lang); } else { $lang = []; } $item = []; $item['uri'] = $this->getURI() . '/' . $latest['apkName']; $item['title'] = $lang['name'] ?? $app['packageName']; $item['title'] .= ' ' . $latest['versionName']; $item['timestamp'] = date(DateTime::ISO8601, (int) ($app['lastUpdated'] / 1000)); if (isset($app['authorName'])) { $item['author'] = $app['authorName']; } if (isset($app['categories'])) { $item['categories'] = $app['categories']; } // Adding Content $icon = $app['icon'] ?? ''; if (!empty($icon)) { $icon = $this->getURI() . '/icons-320/' . $icon; $item['enclosures'] = [$icon]; $icon = ''; } $summary = $lang['summary'] ?? $app['summary'] ?? ''; $description = markdownToHtml(trim($lang['description'] ?? $app['description'] ?? 'None')); $whatsNew = markdownToHtml(trim($lang['whatsNew'] ?? 'None')); $website = $this->createAnchor($lang['webSite'] ?? $app['webSite'] ?? $app['authorWebSite'] ?? null); $source = $this->createAnchor($app['sourceCode'] ?? null); $issueTracker = $this->createAnchor($app['issueTracker'] ?? null); $license = $app['license'] ?? 'None'; $item['content'] = <<{$summary}

Description

{$description}

What's New

{$whatsNew}

Information

Website: {$website}

Source Code: {$source}

Issue Tracker: {$issueTracker}

license: {$app['license']}

EOD; $this->items[] = $item; } } private function getPackage($package) { if (!isset($this->repo['packages'][$package])) { throw new \Exception('Invalid Package Name'); } $package = $this->repo['packages'][$package]; $count = self::ITEM_LIMIT; foreach ($package as $version) { $item = []; $item['uri'] = $this->getURI() . '/' . $version['apkName']; $item['title'] = $version['versionName']; $item['timestamp'] = date(DateTime::ISO8601, (int) ($version['added'] / 1000)); $item['uid'] = (string) $version['versionCode']; $size = round($version['size'] / 1048576, 1); // Bytes -> MB $sdk_link = 'https://developer.android.com/studio/releases/platforms'; $item['content'] = <<size: {$size}MB

Minimum SDK: {$version['minSdkVersion']} (SDK to Android Version List)

hash ({$version['hashType']}): {$version['hash']}

EOD; $this->items[] = $item; if (--$count <= 0) { break; } } } public function getURI() { if (empty($this->queriedContext)) { return parent::getURI(); } $url = rtrim($this->getInput('url'), '/'); if (strstr($url, '?', true)) { return strstr($url, '?', true); } else { return $url; } } public function getName() { if (empty($this->queriedContext)) { return parent::getName(); } $name = $this->repo['repo']['name']; switch ($this->queriedContext) { case 'Latest Updates': return $name; case 'Follow Package': return $this->getInput('package') . ' - ' . $name; default: throw new \Exception('Unimplemented Context (getName)'); } } private function createAnchor($url) { if (empty($url)) { return null; } return sprintf('%s', $url, $url); } }