feat: add unified search integration
All checks were successful
repod / xml (push) Successful in 16s
repod / php (push) Successful in 57s
repod / nodejs (push) Successful in 2m8s
repod / release (push) Has been skipped

This commit is contained in:
Michel Roux 2024-01-18 11:43:58 +01:00
parent d6a9eb0c31
commit c951a93b8c
10 changed files with 136 additions and 35 deletions

View File

@ -11,7 +11,7 @@ You need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) inst
- [x] Sync them with GPodderSync compatible clients - [x] Sync them with GPodderSync compatible clients
- [ ] Import and export subscriptions - [ ] Import and export subscriptions
- [x] Mobile friendly interface - [x] Mobile friendly interface
- [ ] Unified search integration - [x] Unified search integration
- [x] Interface in multiple languages - [x] Interface in multiple languages
## Screenshots ## Screenshots

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace OCA\RePod\AppInfo; namespace OCA\RePod\AppInfo;
use OCA\RePod\Service\SearchProvider;
use OCP\App\AppPathNotFoundException; use OCP\App\AppPathNotFoundException;
use OCP\App\IAppManager; use OCP\App\IAppManager;
use OCP\AppFramework\App; use OCP\AppFramework\App;
@ -43,5 +44,7 @@ class Application extends App implements IBootstrap
$initialState->provideInitialState('gpodder', $gpoddersync); $initialState->provideInitialState('gpodder', $gpoddersync);
} }
public function register(IRegistrationContext $context): void {} public function register(IRegistrationContext $context): void {
$context->registerSearchProvider(SearchProvider::class);
}
} }

View File

@ -19,9 +19,9 @@ class EpisodesController extends Controller
{ {
public function __construct( public function __construct(
IRequest $request, IRequest $request,
private IClientService $clientService,
private EpisodeActionReader $episodeActionReader, private EpisodeActionReader $episodeActionReader,
private EpisodeActionRepository $episodeActionRepository, private EpisodeActionRepository $episodeActionRepository,
private IClientService $clientService,
private UserService $userService private UserService $userService
) { ) {
parent::__construct(Application::APP_ID, $request); parent::__construct(Application::APP_ID, $request);

View File

@ -14,7 +14,10 @@ use OCP\Util;
class PageController extends Controller class PageController extends Controller
{ {
public function __construct(IRequest $request, private IConfig $config) { public function __construct(
IRequest $request,
private IConfig $config
) {
parent::__construct(Application::APP_ID, $request); parent::__construct(Application::APP_ID, $request);
} }

View File

@ -4,41 +4,22 @@ declare(strict_types=1);
namespace OCA\RePod\Controller; namespace OCA\RePod\Controller;
use OCA\GPodderSync\Core\PodcastData\PodcastData;
use OCA\RePod\AppInfo\Application; use OCA\RePod\AppInfo\Application;
use OCA\RePod\Service\FyydService; use OCA\RePod\Service\MultiPodService;
use OCA\RePod\Service\ItunesService;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest; use OCP\IRequest;
use Psr\Log\LoggerInterface;
class SearchController extends Controller class SearchController extends Controller
{ {
public function __construct( public function __construct(
IRequest $request, IRequest $request,
private LoggerInterface $logger, private MultiPodService $multiPodService
private FyydService $fyydService,
private ItunesService $itunesService
) { ) {
parent::__construct(Application::APP_ID, $request); parent::__construct(Application::APP_ID, $request);
} }
public function index(string $value): JSONResponse { public function index(string $value): JSONResponse {
$podcasts = []; return new JSONResponse($this->multiPodService->search($value));
$providers = [$this->fyydService, $this->itunesService];
foreach ($providers as $provider) {
try {
$podcasts = [...$podcasts, ...$provider->search($value)];
} catch (\Exception $e) {
$this->logger->error($e->getMessage(), $e->getTrace());
}
}
usort($podcasts, fn (PodcastData $a, PodcastData $b) => $b->getFetchedAtUnix() <=> $a->getFetchedAtUnix());
$podcasts = array_values(array_intersect_key($podcasts, array_unique(array_map(fn (PodcastData $feed) => $feed->getLink(), $podcasts))));
return new JSONResponse($podcasts);
} }
} }

View File

@ -6,20 +6,16 @@ namespace OCA\RePod\Service;
use OCA\GPodderSync\Core\PodcastData\PodcastData; use OCA\GPodderSync\Core\PodcastData\PodcastData;
use OCP\Http\Client\IClientService; use OCP\Http\Client\IClientService;
use OCP\IUserSession;
use OCP\L10N\IFactory;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
class FyydService implements IProvider class FyydService implements IPodProvider
{ {
private const BASE_URL = 'https://api.fyyd.de/0.2/'; private const BASE_URL = 'https://api.fyyd.de/0.2/';
public function __construct( public function __construct(
private UserService $userService,
private IClientService $clientService, private IClientService $clientService,
private IFactory $l10n, private LoggerInterface $logger,
private IUserSession $userSession, private UserService $userService
private LoggerInterface $logger
) {} ) {}
public function search(string $value): array { public function search(string $value): array {

View File

@ -6,7 +6,7 @@ namespace OCA\RePod\Service;
use OCA\GPodderSync\Core\PodcastData\PodcastData; use OCA\GPodderSync\Core\PodcastData\PodcastData;
interface IProvider interface IPodProvider
{ {
/** /**
* @return PodcastData[] * @return PodcastData[]

View File

@ -7,7 +7,7 @@ namespace OCA\RePod\Service;
use OCA\GPodderSync\Core\PodcastData\PodcastData; use OCA\GPodderSync\Core\PodcastData\PodcastData;
use OCP\Http\Client\IClientService; use OCP\Http\Client\IClientService;
class ItunesService implements IProvider class ItunesService implements IPodProvider
{ {
private const BASE_URL = 'https://itunes.apple.com/'; private const BASE_URL = 'https://itunes.apple.com/';

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace OCA\RePod\Service;
use OCA\GPodderSync\Core\PodcastData\PodcastData;
use Psr\Log\LoggerInterface;
class MultiPodService implements IPodProvider
{
/**
* @var IPodProvider[]
*/
private array $providers = [];
public function __construct(
FyydService $fyydService,
ItunesService $itunesService,
private LoggerInterface $logger
) {
$this->providers = [$fyydService, $itunesService];
}
/**
* @return PodcastData[]
*/
public function search(string $value): array {
$podcasts = [];
foreach ($this->providers as $provider) {
try {
$podcasts = [...$podcasts, ...$provider->search($value)];
} catch (\Exception $e) {
$this->logger->error($e->getMessage(), $e->getTrace());
}
}
usort($podcasts, fn (PodcastData $a, PodcastData $b) => $b->getFetchedAtUnix() <=> $a->getFetchedAtUnix());
return array_values(
array_intersect_key(
$podcasts,
array_unique(
array_map(
fn (PodcastData $feed) => $feed->getLink(),
array_filter($podcasts, fn (PodcastData $feed) => $feed->getLink())
)
)
)
);
}
}

View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace OCA\RePod\Service;
use OCA\RePod\AppInfo\Application;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Search\IProvider;
use OCP\Search\ISearchQuery;
use OCP\Search\SearchResult;
use OCP\Search\SearchResultEntry;
class SearchProvider implements IProvider
{
public function __construct(
private IL10N $l10n,
private IURLGenerator $urlGenerator,
private MultiPodService $multiPodService
) {}
public function getId(): string {
return Application::APP_ID;
}
public function getName(): string {
return $this->l10n->t('Podcasts');
}
public function getOrder(string $route, array $routeParameters): ?int {
if (0 === strpos($route, Application::APP_ID.'.')) {
// Active app, prefer my results
return -1;
}
return 25;
}
public function search(IUser $user, ISearchQuery $query): SearchResult {
$podcasts = $this->multiPodService->search($query->getTerm());
$searchResults = [];
foreach ($podcasts as $podcast) {
$title = $podcast->getTitle();
$link = $podcast->getLink();
if ($title && $link) {
$searchResults[] = new SearchResultEntry(
$podcast->getImageUrl() ?? $this->urlGenerator->linkTo(Application::APP_ID, 'img/app.svg'),
$title,
$podcast->getAuthor() ?? '',
$this->urlGenerator->linkToRoute('repod.page.index').'/#/'.urlencode(base64_encode($link)),
$this->urlGenerator->linkTo(Application::APP_ID, 'img/app.svg')
);
}
}
return SearchResult::complete(
$this->l10n->t('Podcasts'),
$searchResults
);
}
}