mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-01-17 22:28:22 +01:00
b3ac1d176c
* [FDroidRepoBridge] Simplify json retrieval I looked into avoiding the writing-to-file and then reading-from-file altogether. Using a special file path that leaves the data in memory probably wouldn't work. But I'm unsure why we use the `index-v1.jar` file altogether. The main F-Droid repo [lists](https://f-droid.org/en/docs/All_our_APIs/#the-repo-index) not only `index-v1.jar` (which only makes sense if we were to use the contained signature, which we don't), but also `index-v1.json` and `index-v2.json`. These json files can be fetched with `getContents`, optionally cached, and directly fed into `Json::decode` without using a temporary file. The HTTP transfer encoding can compress the file to a similar degree the jar (=zip) can. That's exactly what this commit uses. Now the question is whether all the F-Droid repositories out there have this file. I went through the whole [list of known repositories](https://forum.f-droid.org/t/known-repositories/721) and only one repo misses the `index-v1.json` file: [Bromite](https://fdroid.bromite.org/fdroid/repo/index-v1.json). Under these circumstances we can depend on the availability of the `index-v1.json` file. Closes #4062 * [FDroidRepoBridge] Cleanup not requiring Zip With the last commit 1152386678151aeafd984061d34248023378bf64, the zip extension is not required anymore. Don't fail if it's not available.
198 lines
6.3 KiB
PHP
198 lines
6.3 KiB
PHP
<?php
|
|
|
|
class FDroidRepoBridge extends BridgeAbstract
|
|
{
|
|
const NAME = 'F-Droid Repository Bridge';
|
|
const URI = 'https://f-droid.org/';
|
|
const DESCRIPTION = 'Query any F-Droid Repository for its latest updates.';
|
|
|
|
const ITEM_LIMIT = 50;
|
|
|
|
const PARAMETERS = [
|
|
'global' => [
|
|
'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()
|
|
{
|
|
$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)');
|
|
}
|
|
}
|
|
|
|
private function fetchData()
|
|
{
|
|
$url = $this->getURI();
|
|
$json = getContents($url . '/index-v1.json');
|
|
$data = Json::decode($json);
|
|
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 = '<img src="' . $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'] = <<<EOD
|
|
{$icon}
|
|
<p>{$summary}</p>
|
|
<h1>Description</h1>
|
|
{$description}
|
|
<h1>What's New</h1>
|
|
{$whatsNew}
|
|
<h1>Information</h1>
|
|
<p>Website: {$website}</p>
|
|
<p>Source Code: {$source}</p>
|
|
<p>Issue Tracker: {$issueTracker}</p>
|
|
<p>license: {$app['license']}</p>
|
|
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'] = <<<EOD
|
|
<p>size: {$size}MB</p>
|
|
<p>Minimum SDK: {$version['minSdkVersion']}
|
|
(<a href="{$sdk_link}">SDK to Android Version List</a>)</p>
|
|
<p>hash ({$version['hashType']}): {$version['hash']}</p>
|
|
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('<a href="%s">%s</a>', $url, $url);
|
|
}
|
|
}
|