selfoss icon indicating copy to clipboard operation
selfoss copied to clipboard

Dynamic OPML

Open amazighdotmobi opened this issue 2 years ago • 4 comments

Hi, Would it be possible to work with dynamic OPMLs available online : for example https://diigo2dynopml.e-formation.mobi/conversion.php?url=https%3A%2F%2Fwww.diigo.com%2Frss%2Fuser%2Falexdup44%2FRSS_EDU thank you in advance for your answer

amazighdotmobi avatar Jan 27 '22 07:01 amazighdotmobi

How do you imagine this should work? Would a single source aggregating all of those feeds be okay? Or do you want a single source for each feed in the OPML file.

jtojnar avatar Jan 27 '22 10:01 jtojnar

Thank you very much for the answer. What would be very very useful, would be to aggregate all the feeds from an opml source and make available in addition to the web view, an aggregated feed in rss format.

amazighdotmobi avatar Jan 27 '22 21:01 amazighdotmobi

Copying the following code to src/spouts/rss/DynOpml.php file should work with selfoss 2.19:

<?php

namespace spouts\rss;

use helpers\WebClient;
use Monolog\Logger;
use SimpleXMLElement;

/**
 * Spout for fetching feeds linked in an DynOPML.
 */
class DynOpml extends \spouts\spout {
    /** @var string name of source */
    public $name = 'Dynamic OPML';

    /** @var string description of this source type */
    public $description = 'Get posts from plain RSS/Atom feeds listed in an OPML file.';

    /** @var array configurable parameters */
    public $params = [
        'url' => [
            'title' => 'URL',
            'type' => 'url',
            'default' => '',
            'required' => true,
            'validation' => ['notempty'],
        ],
    ];

    /** @var ?string URL of the source */
    protected $htmlUrl = null;

    /** @var Logger */
    private $logger;

    /** @var feed */
    private $feed;

    /** @var WebClient */
    private $webClient;

    /** @var \Generator<string> */
    private $feedUrls;

    public function __construct(feed $feed, Logger $logger, WebClient $webClient) {
        $this->logger = $logger;
        $this->feed = $feed;
        $this->webClient = $webClient;
    }

    public function load(array $params) {
        if (!function_exists('simplexml_load_string')) {
            throw new \Exception('Missing SimpleXML PHP extension. Please install/enable it as described on https://www.php.net/manual/en/simplexml.installation.php');
        }

        $http = $this->webClient->getHttpClient();
        $this->logger->debug('DynOpml: Loading outline: ' . $params['url']);
        $response = $http->get($params['url']);
        $opml = simplexml_load_string((string) $response->getBody());
        $this->feedUrls = $this->collectFeeds($opml->body);

        $this->htmlUrl = $params['url'];
        $this->spoutTitle = isset($opml->head->title) ? (string) $opml->head->title : null;
    }

    private function collectFeeds(SimpleXMLElement $xml) {
        // parse outline items from the default and selfoss namespaces
        foreach ($xml->xpath('outline') as $outline) {
            if (count($outline->children()) > 0) {
                // outline element has children, recurse into it
                $feeds = $this->collectFeeds($outline);
                // Do not use `yield from`, it re-uses keys.
                foreach ($feeds as $feed) {
                    yield $feed;
                }
            } else {
                $attrs = $outline->attributes(null);
                // RSS URL
                if (isset($attrs->xmlUrl)) {
                    yield (string) $attrs->xmlUrl;
                }
            }
        }
    }

    public function getXmlUrl(array $params) {
        return isset($params['url']) ? html_entity_decode($params['url']) : null;
    }

    public function getHtmlUrl() {
        return $this->htmlUrl;
    }

    public function getIcon() {
        return null;
    }

    /**
     * @return \Generator<Item<SimplePie\Item>> list of items
     */
    public function getItems() {
        foreach ($this->feedUrls as $feedUrl) {
            $this->logger->debug('DynOpml: Fetching subfeed: ' . $feedUrl);
            try {
                $this->feed->load([
                    'url' => $feedUrl,
                ]);

                foreach ($this->feed->getItems() as $item) {
                    // Do not use `yield from`, it re-uses keys.
                    yield $item;
                }
            } catch (\Exception $exception) {
                $this->logger->error('DynOpml: Fetching subfeed ' . $feedUrl . ' failed', ['exception' => $exception]);
            }
        }
    }

    public function destroy() {
        $this->feed->destroy();
        $this->feedUrls = null;
    }
}

jtojnar avatar Oct 16 '22 01:10 jtojnar

Thank you very much, I will test and get back to you as soon as possible.

amazighdotmobi avatar Oct 17 '22 16:10 amazighdotmobi