feat: support itunes namespace in top channel feed (#3776)

Also preserves other properties.
This commit is contained in:
Dag 2024-01-09 20:18:33 +01:00 committed by GitHub
parent ea58c8d2bc
commit 3ce94409ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 298 additions and 203 deletions

View file

@ -100,7 +100,7 @@ class DisplayAction implements ActionInterface
private function createResponse(array $request, BridgeAbstract $bridge, FormatAbstract $format) private function createResponse(array $request, BridgeAbstract $bridge, FormatAbstract $format)
{ {
$items = []; $items = [];
$infos = []; $feed = [];
try { try {
$bridge->loadConfiguration(); $bridge->loadConfiguration();
@ -116,12 +116,7 @@ class DisplayAction implements ActionInterface
} }
$items = $feedItems; $items = $feedItems;
} }
$infos = [ $feed = $bridge->getFeed();
'name' => $bridge->getName(),
'uri' => $bridge->getURI(),
'donationUri' => $bridge->getDonationURI(),
'icon' => $bridge->getIcon()
];
} catch (\Exception $e) { } catch (\Exception $e) {
if ($e instanceof HttpException) { if ($e instanceof HttpException) {
// Reproduce (and log) these responses regardless of error output and report limit // Reproduce (and log) these responses regardless of error output and report limit
@ -155,7 +150,7 @@ class DisplayAction implements ActionInterface
} }
$format->setItems($items); $format->setItems($items);
$format->setExtraInfos($infos); $format->setFeed($feed);
$now = time(); $now = time();
$format->setLastModified($now); $format->setLastModified($now);
$headers = [ $headers = [

View file

@ -280,7 +280,7 @@ class ItakuBridge extends BridgeAbstract
$opt['range'] = ''; $opt['range'] = '';
$user_id = $this->getInput('user_id') ?? $this->getOwnerID($this->getInput('user')); $user_id = $this->getInput('user_id') ?? $this->getOwnerID($this->getInput('user'));
$data = $this->getFeed( $data = $this->getFeedData(
$opt, $opt,
$user_id $user_id
); );
@ -289,7 +289,7 @@ class ItakuBridge extends BridgeAbstract
if ($this->queriedContext === 'Home feed') { if ($this->queriedContext === 'Home feed') {
$opt['order'] = $this->getInput('order'); $opt['order'] = $this->getInput('order');
$opt['range'] = $this->getInput('range'); $opt['range'] = $this->getInput('range');
$data = $this->getFeed($opt); $data = $this->getFeedData($opt);
} }
foreach ($data['results'] as $record) { foreach ($data['results'] as $record) {
@ -409,7 +409,7 @@ class ItakuBridge extends BridgeAbstract
return $this->getData($url, false, true); return $this->getData($url, false, true);
} }
private function getFeed(array $opt, $ownerID = null) private function getFeedData(array $opt, $ownerID = null)
{ {
$url = self::URI . "/api/feed/?date_range={$opt['range']}&ordering={$opt['order']}&page=1&page_size=30&format=json"; $url = self::URI . "/api/feed/?date_range={$opt['range']}&ordering={$opt['order']}&page=1&page_size=30&format=json";

View file

@ -17,44 +17,61 @@ class AtomFormat extends FormatAbstract
public function stringify() public function stringify()
{ {
$document = new \DomDocument('1.0', $this->getCharset()); $document = new \DomDocument('1.0', $this->getCharset());
$document->formatOutput = true;
$feedUrl = get_current_url(); $feedUrl = get_current_url();
$extraInfos = $this->getExtraInfos();
if (empty($extraInfos['uri'])) {
$uri = REPOSITORY;
} else {
$uri = $extraInfos['uri'];
}
$document->formatOutput = true;
$feed = $document->createElementNS(self::ATOM_NS, 'feed'); $feed = $document->createElementNS(self::ATOM_NS, 'feed');
$document->appendChild($feed); $document->appendChild($feed);
$feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:media', self::MRSS_NS); $feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:media', self::MRSS_NS);
$feedArray = $this->getFeed();
foreach ($feedArray as $feedKey => $feedValue) {
if (in_array($feedKey, ['donationUri'])) {
continue;
}
if ($feedKey === 'name') {
$title = $document->createElement('title'); $title = $document->createElement('title');
$feed->appendChild($title); $feed->appendChild($title);
$title->setAttribute('type', 'text'); $title->setAttribute('type', 'text');
$title->appendChild($document->createTextNode($extraInfos['name'])); $title->appendChild($document->createTextNode($feedValue));
} elseif ($feedKey === 'icon') {
if ($feedValue) {
$icon = $document->createElement('icon');
$feed->appendChild($icon);
$icon->appendChild($document->createTextNode($feedValue));
$logo = $document->createElement('logo');
$feed->appendChild($logo);
$logo->appendChild($document->createTextNode($feedValue));
}
} elseif ($feedKey === 'uri') {
if ($feedValue) {
$linkAlternate = $document->createElement('link');
$feed->appendChild($linkAlternate);
$linkAlternate->setAttribute('rel', 'alternate');
$linkAlternate->setAttribute('type', 'text/html');
$linkAlternate->setAttribute('href', $feedValue);
$linkSelf = $document->createElement('link');
$feed->appendChild($linkSelf);
$linkSelf->setAttribute('rel', 'self');
$linkSelf->setAttribute('type', 'application/atom+xml');
$linkSelf->setAttribute('href', $feedUrl);
}
} elseif ($feedKey === 'itunes') {
// todo: skip?
} else {
$element = $document->createElement($feedKey);
$feed->appendChild($element);
$element->appendChild($document->createTextNode($feedValue));
}
}
$id = $document->createElement('id'); $id = $document->createElement('id');
$feed->appendChild($id); $feed->appendChild($id);
$id->appendChild($document->createTextNode($feedUrl)); $id->appendChild($document->createTextNode($feedUrl));
$uriparts = parse_url($uri);
if (empty($extraInfos['icon'])) {
$iconUrl = $uriparts['scheme'] . '://' . $uriparts['host'] . '/favicon.ico';
} else {
$iconUrl = $extraInfos['icon'];
}
$icon = $document->createElement('icon');
$feed->appendChild($icon);
$icon->appendChild($document->createTextNode($iconUrl));
$logo = $document->createElement('logo');
$feed->appendChild($logo);
$logo->appendChild($document->createTextNode($iconUrl));
$feedTimestamp = gmdate(DATE_ATOM, $this->lastModified); $feedTimestamp = gmdate(DATE_ATOM, $this->lastModified);
$updated = $document->createElement('updated'); $updated = $document->createElement('updated');
$feed->appendChild($updated); $feed->appendChild($updated);
@ -69,17 +86,7 @@ class AtomFormat extends FormatAbstract
$author->appendChild($authorName); $author->appendChild($authorName);
$authorName->appendChild($document->createTextNode($feedAuthor)); $authorName->appendChild($document->createTextNode($feedAuthor));
$linkAlternate = $document->createElement('link');
$feed->appendChild($linkAlternate);
$linkAlternate->setAttribute('rel', 'alternate');
$linkAlternate->setAttribute('type', 'text/html');
$linkAlternate->setAttribute('href', $uri);
$linkSelf = $document->createElement('link');
$feed->appendChild($linkSelf);
$linkSelf->setAttribute('rel', 'self');
$linkSelf->setAttribute('type', 'application/atom+xml');
$linkSelf->setAttribute('href', $feedUrl);
foreach ($this->getItems() as $item) { foreach ($this->getItems() as $item) {
$itemArray = $item->toArray(); $itemArray = $item->toArray();

View file

@ -8,7 +8,7 @@ class HtmlFormat extends FormatAbstract
{ {
$queryString = $_SERVER['QUERY_STRING']; $queryString = $_SERVER['QUERY_STRING'];
$extraInfos = $this->getExtraInfos(); $feedArray = $this->getFeed();
$formatFactory = new FormatFactory(); $formatFactory = new FormatFactory();
$buttons = []; $buttons = [];
$linkTags = []; $linkTags = [];
@ -29,9 +29,9 @@ class HtmlFormat extends FormatAbstract
]; ];
} }
if (Configuration::getConfig('admin', 'donations') && $extraInfos['donationUri'] !== '') { if (Configuration::getConfig('admin', 'donations') && $feedArray['donationUri']) {
$buttons[] = [ $buttons[] = [
'href' => e($extraInfos['donationUri']), 'href' => e($feedArray['donationUri']),
'value' => 'Donate to maintainer', 'value' => 'Donate to maintainer',
]; ];
} }
@ -39,7 +39,7 @@ class HtmlFormat extends FormatAbstract
$items = []; $items = [];
foreach ($this->getItems() as $item) { foreach ($this->getItems() as $item) {
$items[] = [ $items[] = [
'url' => $item->getURI() ?: $extraInfos['uri'], 'url' => $item->getURI() ?: $feedArray['uri'],
'title' => $item->getTitle() ?? '(no title)', 'title' => $item->getTitle() ?? '(no title)',
'timestamp' => $item->getTimestamp(), 'timestamp' => $item->getTimestamp(),
'author' => $item->getAuthor(), 'author' => $item->getAuthor(),
@ -51,9 +51,9 @@ class HtmlFormat extends FormatAbstract
$html = render_template(__DIR__ . '/../templates/html-format.html.php', [ $html = render_template(__DIR__ . '/../templates/html-format.html.php', [
'charset' => $this->getCharset(), 'charset' => $this->getCharset(),
'title' => $extraInfos['name'], 'title' => $feedArray['name'],
'linkTags' => $linkTags, 'linkTags' => $linkTags,
'uri' => $extraInfos['uri'], 'uri' => $feedArray['uri'],
'buttons' => $buttons, 'buttons' => $buttons,
'items' => $items, 'items' => $items,
]); ]);

View file

@ -25,18 +25,18 @@ class JsonFormat extends FormatAbstract
public function stringify() public function stringify()
{ {
$host = $_SERVER['HTTP_HOST'] ?? ''; $feedArray = $this->getFeed();
$extraInfos = $this->getExtraInfos();
$data = [ $data = [
'version' => 'https://jsonfeed.org/version/1', 'version' => 'https://jsonfeed.org/version/1',
'title' => empty($extraInfos['name']) ? $host : $extraInfos['name'], 'title' => $feedArray['name'],
'home_page_url' => empty($extraInfos['uri']) ? REPOSITORY : $extraInfos['uri'], 'home_page_url' => $feedArray['uri'],
'feed_url' => get_current_url(), 'feed_url' => get_current_url(),
]; ];
if (!empty($extraInfos['icon'])) { if ($feedArray['icon']) {
$data['icon'] = $extraInfos['icon']; $data['icon'] = $feedArray['icon'];
$data['favicon'] = $extraInfos['icon']; $data['favicon'] = $feedArray['icon'];
} }
$items = []; $items = [];

View file

@ -35,16 +35,8 @@ class MrssFormat extends FormatAbstract
public function stringify() public function stringify()
{ {
$document = new \DomDocument('1.0', $this->getCharset()); $document = new \DomDocument('1.0', $this->getCharset());
$feedUrl = get_current_url();
$extraInfos = $this->getExtraInfos();
if (empty($extraInfos['uri'])) {
$uri = REPOSITORY;
} else {
$uri = $extraInfos['uri'];
}
$document->formatOutput = true; $document->formatOutput = true;
$feed = $document->createElement('rss'); $feed = $document->createElement('rss');
$document->appendChild($feed); $document->appendChild($feed);
$feed->setAttribute('version', '2.0'); $feed->setAttribute('version', '2.0');
@ -54,26 +46,48 @@ class MrssFormat extends FormatAbstract
$channel = $document->createElement('channel'); $channel = $document->createElement('channel');
$feed->appendChild($channel); $feed->appendChild($channel);
$title = $extraInfos['name']; $feedArray = $this->getFeed();
$uri = $feedArray['uri'];
$title = $feedArray['name'];
foreach ($feedArray as $feedKey => $feedValue) {
if (in_array($feedKey, ['atom', 'donationUri'])) {
continue;
}
if ($feedKey === 'name') {
$channelTitle = $document->createElement('title'); $channelTitle = $document->createElement('title');
$channel->appendChild($channelTitle); $channel->appendChild($channelTitle);
$channelTitle->appendChild($document->createTextNode($title)); $channelTitle->appendChild($document->createTextNode($title));
$description = $document->createElement('description');
$channel->appendChild($description);
$description->appendChild($document->createTextNode($title));
} elseif ($feedKey === 'uri') {
$link = $document->createElement('link'); $link = $document->createElement('link');
$channel->appendChild($link); $channel->appendChild($link);
$link->appendChild($document->createTextNode($uri)); $link->appendChild($document->createTextNode($uri));
$description = $document->createElement('description'); $linkAlternate = $document->createElementNS(self::ATOM_NS, 'link');
$channel->appendChild($description); $channel->appendChild($linkAlternate);
$description->appendChild($document->createTextNode($extraInfos['name'])); $linkAlternate->setAttribute('rel', 'alternate');
$linkAlternate->setAttribute('type', 'text/html');
$linkAlternate->setAttribute('href', $uri);
$linkSelf = $document->createElementNS(self::ATOM_NS, 'link');
$channel->appendChild($linkSelf);
$linkSelf->setAttribute('rel', 'self');
$linkSelf->setAttribute('type', 'application/atom+xml');
$feedUrl = get_current_url();
$linkSelf->setAttribute('href', $feedUrl);
} elseif ($feedKey === 'icon') {
$allowedIconExtensions = [ $allowedIconExtensions = [
'.gif', '.gif',
'.jpg', '.jpg',
'.png', '.png',
'.ico',
]; ];
$icon = $extraInfos['icon']; $icon = $feedValue;
if (!empty($icon) && in_array(substr($icon, -4), $allowedIconExtensions)) { if ($icon && in_array(substr($icon, -4), $allowedIconExtensions)) {
$feedImage = $document->createElement('image'); $feedImage = $document->createElement('image');
$channel->appendChild($feedImage); $channel->appendChild($feedImage);
$iconUrl = $document->createElement('url'); $iconUrl = $document->createElement('url');
@ -86,18 +100,19 @@ class MrssFormat extends FormatAbstract
$iconLink->appendChild($document->createTextNode($uri)); $iconLink->appendChild($document->createTextNode($uri));
$feedImage->appendChild($iconLink); $feedImage->appendChild($iconLink);
} }
} elseif ($feedKey === 'itunes') {
$linkAlternate = $document->createElementNS(self::ATOM_NS, 'link'); $feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:itunes', self::ITUNES_NS);
$channel->appendChild($linkAlternate); foreach ($feedValue as $itunesKey => $itunesValue) {
$linkAlternate->setAttribute('rel', 'alternate'); $itunesProperty = $document->createElementNS(self::ITUNES_NS, $itunesKey);
$linkAlternate->setAttribute('type', 'text/html'); $channel->appendChild($itunesProperty);
$linkAlternate->setAttribute('href', $uri); $itunesProperty->appendChild($document->createTextNode($itunesValue));
}
$linkSelf = $document->createElementNS(self::ATOM_NS, 'link'); } else {
$channel->appendChild($linkSelf); $element = $document->createElement($feedKey);
$linkSelf->setAttribute('rel', 'self'); $channel->appendChild($element);
$linkSelf->setAttribute('type', 'application/atom+xml'); $element->appendChild($document->createTextNode($feedValue));
$linkSelf->setAttribute('href', $feedUrl); }
}
foreach ($this->getItems() as $item) { foreach ($this->getItems() as $item) {
$itemArray = $item->toArray(); $itemArray = $item->toArray();
@ -135,6 +150,7 @@ class MrssFormat extends FormatAbstract
$entry->appendChild($itunesProperty); $entry->appendChild($itunesProperty);
$itunesProperty->appendChild($document->createTextNode($itunesValue)); $itunesProperty->appendChild($document->createTextNode($itunesValue));
} }
if (isset($itemArray['enclosure'])) { if (isset($itemArray['enclosure'])) {
$itunesEnclosure = $document->createElement('enclosure'); $itunesEnclosure = $document->createElement('enclosure');
$entry->appendChild($itunesEnclosure); $entry->appendChild($itunesEnclosure);
@ -142,7 +158,9 @@ class MrssFormat extends FormatAbstract
$itunesEnclosure->setAttribute('length', $itemArray['enclosure']['length']); $itunesEnclosure->setAttribute('length', $itemArray['enclosure']['length']);
$itunesEnclosure->setAttribute('type', $itemArray['enclosure']['type']); $itunesEnclosure->setAttribute('type', $itemArray['enclosure']['type']);
} }
} if (!empty($itemUri)) { }
if (!empty($itemUri)) {
$entryLink = $document->createElement('link'); $entryLink = $document->createElement('link');
$entry->appendChild($entryLink); $entry->appendChild($entryLink);
$entryLink->appendChild($document->createTextNode($itemUri)); $entryLink->appendChild($document->createTextNode($itemUri));

View file

@ -6,11 +6,11 @@ class PlaintextFormat extends FormatAbstract
public function stringify() public function stringify()
{ {
$data = []; $feed = $this->getFeed();
foreach ($this->getItems() as $item) { foreach ($this->getItems() as $item) {
$data[] = $item->toArray(); $feed['items'][] = $item->toArray();
} }
$text = print_r($data, true); $text = print_r($feed, true);
// Remove invalid non-UTF8 characters // Remove invalid non-UTF8 characters
ini_set('mbstring.substitute_character', 'none'); ini_set('mbstring.substitute_character', 'none');
$text = mb_convert_encoding($text, $this->getCharset(), 'UTF-8'); $text = mb_convert_encoding($text, $this->getCharset(), 'UTF-8');

View file

@ -40,9 +40,38 @@ abstract class BridgeAbstract
abstract public function collectData(); abstract public function collectData();
public function getItems() public function getFeed(): array
{ {
return $this->items; return [
'name' => $this->getName(),
'uri' => $this->getURI(),
'donationUri' => $this->getDonationURI(),
'icon' => $this->getIcon(),
];
}
public function getName()
{
return static::NAME;
}
public function getURI()
{
return static::URI ?? 'https://github.com/RSS-Bridge/rss-bridge/';
}
public function getDonationURI(): string
{
return static::DONATION_URI;
}
public function getIcon()
{
if (static::URI) {
// This favicon may or may not exist
return rtrim(static::URI, '/') . '/favicon.ico';
}
return '';
} }
public function getOption(string $name) public function getOption(string $name)
@ -50,6 +79,9 @@ abstract class BridgeAbstract
return $this->configuration[$name] ?? null; return $this->configuration[$name] ?? null;
} }
/**
* The description is currently not used in feed production
*/
public function getDescription() public function getDescription()
{ {
return static::DESCRIPTION; return static::DESCRIPTION;
@ -60,29 +92,14 @@ abstract class BridgeAbstract
return static::MAINTAINER; return static::MAINTAINER;
} }
public function getName()
{
return static::NAME;
}
public function getIcon()
{
return static::URI . '/favicon.ico';
}
public function getParameters(): array public function getParameters(): array
{ {
return static::PARAMETERS; return static::PARAMETERS;
} }
public function getURI() public function getItems()
{ {
return static::URI; return $this->items;
}
public function getDonationURI(): string
{
return static::DONATION_URI;
} }
public function getCacheTimeout() public function getCacheTimeout()

View file

@ -9,10 +9,43 @@ abstract class FormatAbstract
protected string $charset = 'UTF-8'; protected string $charset = 'UTF-8';
protected array $items = []; protected array $items = [];
protected int $lastModified; protected int $lastModified;
protected array $extraInfos = [];
protected array $feed = [];
abstract public function stringify(); abstract public function stringify();
public function setFeed(array $feed)
{
$default = [
'name' => '',
'uri' => '',
'icon' => '',
'donationUri' => '',
];
$this->feed = array_merge($default, $feed);
}
public function getFeed(): array
{
return $this->feed;
}
/**
* @param FeedItem[] $items
*/
public function setItems(array $items): void
{
$this->items = $items;
}
/**
* @return FeedItem[] The items
*/
public function getItems(): array
{
return $this->items;
}
public function getMimeType(): string public function getMimeType(): string
{ {
return static::MIME_TYPE; return static::MIME_TYPE;
@ -32,44 +65,4 @@ abstract class FormatAbstract
{ {
$this->lastModified = $lastModified; $this->lastModified = $lastModified;
} }
/**
* @param FeedItem[] $items
*/
public function setItems(array $items): void
{
$this->items = $items;
}
/**
* @return FeedItem[] The items
*/
public function getItems(): array
{
return $this->items;
}
public function setExtraInfos(array $infos = [])
{
$extras = [
'name',
'uri',
'icon',
'donationUri',
];
foreach ($extras as $extra) {
if (!isset($infos[$extra])) {
$infos[$extra] = '';
}
}
$this->extraInfos = $infos;
}
public function getExtraInfos(): array
{
if (!$this->extraInfos) {
$this->setExtraInfos();
}
return $this->extraInfos;
}
} }

View file

@ -9,9 +9,6 @@ const PATH_LIB_CACHES = __DIR__ . '/../caches/';
/** Path to the cache folder */ /** Path to the cache folder */
const PATH_CACHE = __DIR__ . '/../cache/'; const PATH_CACHE = __DIR__ . '/../cache/';
/** URL to the RSS-Bridge repository */
const REPOSITORY = 'https://github.com/RSS-Bridge/rss-bridge/';
// Allow larger files for simple_html_dom // Allow larger files for simple_html_dom
// todo: extract to config (if possible) // todo: extract to config (if possible)
const MAX_FILE_SIZE = 10000000; const MAX_FILE_SIZE = 10000000;

72
tests/FormatTest.php Normal file
View file

@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace RssBridge\Tests;
use PHPUnit\Framework\TestCase;
class FormatTest extends TestCase
{
public function testBridge()
{
$sut = new \MrssFormat();
$expected = [
'name' => '',
'uri' => '',
'icon' => '',
'donationUri' => '',
];
$this->assertEquals([], $sut->getFeed());
$sut->setFeed([
'name' => '0',
'uri' => '1',
'icon' => '2',
'donationUri' => '3',
]);
$expected = [
'name' => '0',
'uri' => '1',
'icon' => '2',
'donationUri' => '3',
];
$this->assertEquals($expected, $sut->getFeed());
$sut->setFeed([]);
$expected = [
'name' => '',
'uri' => '',
'icon' => '',
'donationUri' => '',
];
$this->assertEquals($expected, $sut->getFeed());
$sut->setFeed(['foo' => 'bar', 'foo2' => 'bar2']);
$expected = [
'name' => '',
'uri' => '',
'icon' => '',
'donationUri' => '',
'foo' => 'bar',
'foo2' => 'bar2',
];
$this->assertEquals($expected, $sut->getFeed());
}
}
class TestFormat extends \FormatAbstract
{
public function stringify()
{
}
}
class TestBridge extends \BridgeAbstract
{
public function collectData()
{
$this->items[] = ['title' => 'kek'];
}
}

View file

@ -61,7 +61,7 @@ abstract class BaseFormatTest extends TestCase
$formatFactory = new FormatFactory(); $formatFactory = new FormatFactory();
$format = $formatFactory->create($formatName); $format = $formatFactory->create($formatName);
$format->setItems($sample->items); $format->setItems($sample->items);
$format->setExtraInfos($sample->meta); $format->setFeed($sample->meta);
$format->setLastModified(strtotime('2000-01-01 12:00:00 UTC')); $format->setLastModified(strtotime('2000-01-01 12:00:00 UTC'));
return $format->stringify(); return $format->stringify();

View file

@ -2,15 +2,15 @@
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<title type="text">Sample feed with common data</title> <title type="text">Sample feed with common data</title>
<id>https://example.com/feed?type=common&amp;items=4</id> <link href="https://example.com/blog/" rel="alternate" type="text/html"/>
<link href="https://example.com/feed?type=common&amp;items=4" rel="self" type="application/atom+xml"/>
<icon>https://example.com/logo.png</icon> <icon>https://example.com/logo.png</icon>
<logo>https://example.com/logo.png</logo> <logo>https://example.com/logo.png</logo>
<id>https://example.com/feed?type=common&amp;items=4</id>
<updated>2000-01-01T12:00:00+00:00</updated> <updated>2000-01-01T12:00:00+00:00</updated>
<author> <author>
<name>RSS-Bridge</name> <name>RSS-Bridge</name>
</author> </author>
<link href="https://example.com/blog/" rel="alternate" type="text/html"/>
<link href="https://example.com/feed?type=common&amp;items=4" rel="self" type="application/atom+xml"/>
<entry> <entry>
<title type="html">Test Entry</title> <title type="html">Test Entry</title>

View file

@ -2,14 +2,12 @@
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<title type="text">Sample feed with minimum data</title> <title type="text">Sample feed with minimum data</title>
<link href="https://github.com/RSS-Bridge/rss-bridge/" rel="alternate" type="text/html"/>
<link href="https://example.com/feed" rel="self" type="application/atom+xml"/>
<id>https://example.com/feed</id> <id>https://example.com/feed</id>
<icon>https://github.com/favicon.ico</icon>
<logo>https://github.com/favicon.ico</logo>
<updated>2000-01-01T12:00:00+00:00</updated> <updated>2000-01-01T12:00:00+00:00</updated>
<author> <author>
<name>RSS-Bridge</name> <name>RSS-Bridge</name>
</author> </author>
<link href="https://github.com/RSS-Bridge/rss-bridge/" rel="alternate" type="text/html"/>
<link href="https://example.com/feed" rel="self" type="application/atom+xml"/>
</feed> </feed>

View file

@ -2,15 +2,13 @@
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<title type="text">Sample feed with minimum data</title> <title type="text">Sample feed with minimum data</title>
<link href="https://github.com/RSS-Bridge/rss-bridge/" rel="alternate" type="text/html"/>
<link href="https://example.com/feed" rel="self" type="application/atom+xml"/>
<id>https://example.com/feed</id> <id>https://example.com/feed</id>
<icon>https://github.com/favicon.ico</icon>
<logo>https://github.com/favicon.ico</logo>
<updated>2000-01-01T12:00:00+00:00</updated> <updated>2000-01-01T12:00:00+00:00</updated>
<author> <author>
<name>RSS-Bridge</name> <name>RSS-Bridge</name>
</author> </author>
<link href="https://github.com/RSS-Bridge/rss-bridge/" rel="alternate" type="text/html"/>
<link href="https://example.com/feed" rel="self" type="application/atom+xml"/>
<entry> <entry>
<title type="html">Sample Item #1</title> <title type="html">Sample Item #1</title>

View file

@ -2,15 +2,15 @@
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<title type="text">Sample microblog feed</title> <title type="text">Sample microblog feed</title>
<id>https://example.com/feed</id> <link href="https://example.com/blog/" rel="alternate" type="text/html"/>
<link href="https://example.com/feed" rel="self" type="application/atom+xml"/>
<icon>https://example.com/logo.png</icon> <icon>https://example.com/logo.png</icon>
<logo>https://example.com/logo.png</logo> <logo>https://example.com/logo.png</logo>
<id>https://example.com/feed</id>
<updated>2000-01-01T12:00:00+00:00</updated> <updated>2000-01-01T12:00:00+00:00</updated>
<author> <author>
<name>RSS-Bridge</name> <name>RSS-Bridge</name>
</author> </author>
<link href="https://example.com/blog/" rel="alternate" type="text/html"/>
<link href="https://example.com/feed" rel="self" type="application/atom+xml"/>
<entry> <entry>
<title type="html">Oh 😲 I found three monkeys 🙈🙉🙊</title> <title type="html">Oh 😲 I found three monkeys 🙈🙉🙊</title>

View file

@ -2,15 +2,15 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<channel> <channel>
<title>Sample feed with common data</title> <title>Sample feed with common data</title>
<link>https://example.com/blog/</link>
<description>Sample feed with common data</description> <description>Sample feed with common data</description>
<link>https://example.com/blog/</link>
<atom:link href="https://example.com/blog/" rel="alternate" type="text/html"/>
<atom:link href="https://example.com/feed?type=common&amp;items=4" rel="self" type="application/atom+xml"/>
<image> <image>
<url>https://example.com/logo.png</url> <url>https://example.com/logo.png</url>
<title>Sample feed with common data</title> <title>Sample feed with common data</title>
<link>https://example.com/blog/</link> <link>https://example.com/blog/</link>
</image> </image>
<atom:link href="https://example.com/blog/" rel="alternate" type="text/html"/>
<atom:link href="https://example.com/feed?type=common&amp;items=4" rel="self" type="application/atom+xml"/>
<item> <item>
<title>Test Entry</title> <title>Test Entry</title>

View file

@ -2,8 +2,8 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<channel> <channel>
<title>Sample feed with minimum data</title> <title>Sample feed with minimum data</title>
<link>https://github.com/RSS-Bridge/rss-bridge/</link>
<description>Sample feed with minimum data</description> <description>Sample feed with minimum data</description>
<link>https://github.com/RSS-Bridge/rss-bridge/</link>
<atom:link href="https://github.com/RSS-Bridge/rss-bridge/" rel="alternate" type="text/html"/> <atom:link href="https://github.com/RSS-Bridge/rss-bridge/" rel="alternate" type="text/html"/>
<atom:link href="https://example.com/feed" rel="self" type="application/atom+xml"/> <atom:link href="https://example.com/feed" rel="self" type="application/atom+xml"/>
</channel> </channel>

View file

@ -2,8 +2,8 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<channel> <channel>
<title>Sample feed with minimum data</title> <title>Sample feed with minimum data</title>
<link>https://github.com/RSS-Bridge/rss-bridge/</link>
<description>Sample feed with minimum data</description> <description>Sample feed with minimum data</description>
<link>https://github.com/RSS-Bridge/rss-bridge/</link>
<atom:link href="https://github.com/RSS-Bridge/rss-bridge/" rel="alternate" type="text/html"/> <atom:link href="https://github.com/RSS-Bridge/rss-bridge/" rel="alternate" type="text/html"/>
<atom:link href="https://example.com/feed" rel="self" type="application/atom+xml"/> <atom:link href="https://example.com/feed" rel="self" type="application/atom+xml"/>

View file

@ -2,15 +2,15 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<channel> <channel>
<title>Sample microblog feed</title> <title>Sample microblog feed</title>
<link>https://example.com/blog/</link>
<description>Sample microblog feed</description> <description>Sample microblog feed</description>
<link>https://example.com/blog/</link>
<atom:link href="https://example.com/blog/" rel="alternate" type="text/html"/>
<atom:link href="https://example.com/feed" rel="self" type="application/atom+xml"/>
<image> <image>
<url>https://example.com/logo.png</url> <url>https://example.com/logo.png</url>
<title>Sample microblog feed</title> <title>Sample microblog feed</title>
<link>https://example.com/blog/</link> <link>https://example.com/blog/</link>
</image> </image>
<atom:link href="https://example.com/blog/" rel="alternate" type="text/html"/>
<atom:link href="https://example.com/feed" rel="self" type="application/atom+xml"/>
<item> <item>
<guid isPermaLink="false">1918f084648b82057c1dd3faa3d091da82a6fac2</guid> <guid isPermaLink="false">1918f084648b82057c1dd3faa3d091da82a6fac2</guid>

View file

@ -6,7 +6,7 @@
}, },
"meta": { "meta": {
"name": "Sample feed with minimum data", "name": "Sample feed with minimum data",
"uri": "", "uri": "https://github.com/RSS-Bridge/rss-bridge/",
"icon": "" "icon": ""
}, },
"items": [] "items": []

View file

@ -6,7 +6,7 @@
}, },
"meta": { "meta": {
"name": "Sample feed with minimum data", "name": "Sample feed with minimum data",
"uri": "", "uri": "https://github.com/RSS-Bridge/rss-bridge/",
"icon": "" "icon": ""
}, },
"items": [ "items": [