Mark Pilgrim
diff --git a/tests/samples/expectedJsonFormat/feed.common.json b/tests/samples/expectedJsonFormat/feed.common.json
index 5a0e1b5a..0b3e97b0 100644
--- a/tests/samples/expectedJsonFormat/feed.common.json
+++ b/tests/samples/expectedJsonFormat/feed.common.json
@@ -26,7 +26,7 @@
},
"content_html": "We — Manton Reece and Brent Simmons — have noticed that JSON has become the developers’ choice for APIs, and that developers will often go out of their way to avoid XML. JSON is simpler to read and write, and it’s less prone to bugs.
\n\nSo we developed JSON Feed, a format similar to RSS and Atom but in JSON. It reflects the lessons learned from our years of work reading and publishing feeds.
\n\nSee the spec. It’s at version 1, which may be the only version ever needed. If future versions are needed, version 1 feeds will still be valid feeds.
\n\nNotes
\n\nWe have a WordPress plugin and, coming soon, a JSON Feed Parser for Swift. As more code is written, by us and others, we’ll update the code page.
\n\nSee Mapping RSS and Atom to JSON Feed for more on the similarities between the formats.
\n\nThis website — the Markdown files and supporting resources — is up on GitHub, and you’re welcome to comment there.
\n\nThis website is also a blog, and you can subscribe to the RSS feed or the JSON feed (if your reader supports it).
\n\nWe worked with a number of people on this over the course of several months. We list them, and thank them, at the bottom of the spec. But — most importantly — Craig Hockenberry spent a little time making it look pretty. :)
"
},{
- "id": "http://example.org/2005/04/02/atom",
+ "id": "dd6b6c920d3b340ab9e07faf6682f2a7c4f70134",
"url": "http://example.org/2005/04/02/atom",
"title": "Atom draft-07 snapshot",
"date_modified": "2005-07-31T12:29:29+00:00",
diff --git a/tests/samples/feed.common.json b/tests/samples/feed.common.json
index be0c56a9..080ba01a 100644
--- a/tests/samples/feed.common.json
+++ b/tests/samples/feed.common.json
@@ -25,6 +25,7 @@
"content": "We — Manton Reece and Brent Simmons — have noticed that JSON has become the developers’ choice for APIs, and that developers will often go out of their way to avoid XML. JSON is simpler to read and write, and it’s less prone to bugs.
\n\nSo we developed JSON Feed, a format similar to RSS and Atom but in JSON. It reflects the lessons learned from our years of work reading and publishing feeds.
\n\nSee the spec. It’s at version 1, which may be the only version ever needed. If future versions are needed, version 1 feeds will still be valid feeds.
\n\nNotes
\n\nWe have a WordPress plugin and, coming soon, a JSON Feed Parser for Swift. As more code is written, by us and others, we’ll update the code page.
\n\nSee Mapping RSS and Atom to JSON Feed for more on the similarities between the formats.
\n\nThis website — the Markdown files and supporting resources — is up on GitHub, and you’re welcome to comment there.
\n\nThis website is also a blog, and you can subscribe to the RSS feed or the JSON feed (if your reader supports it).
\n\nWe worked with a number of people on this over the course of several months. We list them, and thank them, at the bottom of the spec. But — most importantly — Craig Hockenberry spent a little time making it look pretty. :)
"
},{
"uri": "http://example.org/2005/04/02/atom",
+ "uid": "tag:example.org,2003:3.2397",
"title": "Atom draft-07 snapshot",
"timestamp": 1122812969,
"author": "Mark Pilgrim",
From 25593d9c1852b3c7610cc57688258a8f7216f1e1 Mon Sep 17 00:00:00 2001
From: triatic <42704418+triatic@users.noreply.github.com>
Date: Mon, 4 Feb 2019 13:56:07 +0000
Subject: [PATCH 07/66] [TwitterBridge] Append username of retweeter to author
(#1016)
Append username of retweeter to author. Useful when viewing all unread tweets in an RSS reader which are not sorted within username folders.
---
bridges/TwitterBridge.php | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/bridges/TwitterBridge.php b/bridges/TwitterBridge.php
index 32ed9428..b3b7bed4 100644
--- a/bridges/TwitterBridge.php
+++ b/bridges/TwitterBridge.php
@@ -165,7 +165,7 @@ class TwitterBridge extends BridgeAbstract {
// Skip retweets?
if($this->getInput('noretweet')
- && $tweet->getAttribute('data-screen-name') !== $this->getInput('u')) {
+ && strcasecmp($tweet->getAttribute('data-screen-name'), $this->getInput('u'))) {
continue;
}
@@ -189,6 +189,9 @@ class TwitterBridge extends BridgeAbstract {
$item['fullname'] = htmlspecialchars_decode($tweet->getAttribute('data-name'), ENT_QUOTES);
// get author
$item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')';
+ if(strcasecmp($tweet->getAttribute('data-screen-name'), $this->getInput('u'))) {
+ $item['author'] .= ' RT: @' . $this->getInput('u');
+ }
// get avatar link
$item['avatar'] = $tweet->find('img', 0)->src;
// get TweetID
From f65a4076badc983cac65826f05d180a436348d72 Mon Sep 17 00:00:00 2001
From: fulmeek <36341513+fulmeek@users.noreply.github.com>
Date: Mon, 4 Feb 2019 14:58:11 +0100
Subject: [PATCH 08/66] [CacheImplementationTest] Add unit tests for cache
implementations (#1007)
---
tests/CacheImplementationTest.php | 44 +++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
create mode 100644 tests/CacheImplementationTest.php
diff --git a/tests/CacheImplementationTest.php b/tests/CacheImplementationTest.php
new file mode 100644
index 00000000..7eb1af51
--- /dev/null
+++ b/tests/CacheImplementationTest.php
@@ -0,0 +1,44 @@
+setCache($path);
+ $this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character');
+ $this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces');
+ $this->assertStringEndsWith('Cache', $this->class, 'class name must end with "Cache"');
+ }
+
+ /**
+ * @dataProvider dataCachesProvider
+ */
+ public function testClassType($path) {
+ $this->setCache($path);
+ $this->assertInstanceOf(CacheInterface::class, $this->obj);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function dataCachesProvider() {
+ $caches = array();
+ foreach (glob(PATH_LIB_CACHES . '*.php') as $path) {
+ $caches[basename($path, '.php')] = array($path);
+ }
+ return $caches;
+ }
+
+ private function setCache($path) {
+ require_once $path;
+ $this->class = basename($path, '.php');
+ $this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist');
+ $this->obj = new $this->class();
+ }
+}
From 11a39af35cfa63ed6cc2d982f7116a7dbc3d0912 Mon Sep 17 00:00:00 2001
From: fulmeek <36341513+fulmeek@users.noreply.github.com>
Date: Mon, 4 Feb 2019 14:59:09 +0100
Subject: [PATCH 09/66] [FormatImplementationTest] Add unit tests for format
implementations (#1008)
---
tests/FormatImplementationTest.php | 44 ++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
create mode 100644 tests/FormatImplementationTest.php
diff --git a/tests/FormatImplementationTest.php b/tests/FormatImplementationTest.php
new file mode 100644
index 00000000..f2df6350
--- /dev/null
+++ b/tests/FormatImplementationTest.php
@@ -0,0 +1,44 @@
+setFormat($path);
+ $this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character');
+ $this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces');
+ $this->assertStringEndsWith('Format', $this->class, 'class name must end with "Format"');
+ }
+
+ /**
+ * @dataProvider dataFormatsProvider
+ */
+ public function testClassType($path) {
+ $this->setFormat($path);
+ $this->assertInstanceOf(FormatInterface::class, $this->obj);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function dataFormatsProvider() {
+ $formats = array();
+ foreach (glob(PATH_LIB_FORMATS . '*.php') as $path) {
+ $formats[basename($path, '.php')] = array($path);
+ }
+ return $formats;
+ }
+
+ private function setFormat($path) {
+ require_once $path;
+ $this->class = basename($path, '.php');
+ $this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist');
+ $this->obj = new $this->class();
+ }
+}
From 0063d2c376e8e2bbae3e5796187367628072a34f Mon Sep 17 00:00:00 2001
From: fulmeek <36341513+fulmeek@users.noreply.github.com>
Date: Mon, 4 Feb 2019 15:33:13 +0100
Subject: [PATCH 10/66] [HtmlFormat] minor typographical fix-ups (#1009)
---
static/HtmlFormat.css | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/static/HtmlFormat.css b/static/HtmlFormat.css
index e17e3255..5b9fd2e1 100644
--- a/static/HtmlFormat.css
+++ b/static/HtmlFormat.css
@@ -32,7 +32,6 @@ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockq
width: 60%;
margin: 30px auto;
padding: 15px 15px;
- text-align: center;
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.09);
border-radius: 4px;
}
@@ -59,6 +58,18 @@ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockq
}
section > div.content, section > div.attachments {
padding: 10px;
+}
+ section h1, section h2, section h3, section b, section strong {
+ font-weight: bold;
+}
+ section i, section em {
+ font-style: italic;
+}
+ section p:not(:last-child) {
+ margin-bottom: 1em;
+}
+ section li {
+ margin-left: 1em;
}
section > div.attachments > li.enclosure {
list-style-type: circle;
@@ -84,4 +95,4 @@ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockq
}
button:hover {
background: #49afff;
-}
\ No newline at end of file
+}
From 32d4da8b76ae03806288a082a4fac8efeed68624 Mon Sep 17 00:00:00 2001
From: logmanoriginal
Date: Mon, 4 Feb 2019 17:35:40 +0100
Subject: [PATCH 11/66] [Bridge] Fix failed to open stream when reading
non-existing whitelist
---
lib/Bridge.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/Bridge.php b/lib/Bridge.php
index c9561c8f..9e5750a7 100644
--- a/lib/Bridge.php
+++ b/lib/Bridge.php
@@ -213,7 +213,7 @@ class Bridge {
// Create initial whitelist or load from disk
if (!file_exists(WHITELIST) && !empty(self::$whitelist)) {
file_put_contents(WHITELIST, implode("\n", self::$whitelist));
- } else {
+ } elseif(file_exists(WHITELIST)) {
$contents = trim(file_get_contents(WHITELIST));
From 80f6a8b3d4dbb1d37225959283a8ff2c51fc9ef8 Mon Sep 17 00:00:00 2001
From: fulmeek <36341513+fulmeek@users.noreply.github.com>
Date: Wed, 6 Feb 2019 17:18:33 +0100
Subject: [PATCH 12/66] [MrssFormat] Rework to make it valid RSS 2.0 + Media
RSS (#996)
---
formats/MrssFormat.php | 136 ++++++++++++------
tests/MrssFormatTest.php | 90 ++++++++++++
.../expectedMrssFormat/feed.common.xml | 64 +++++++++
.../samples/expectedMrssFormat/feed.empty.xml | 10 ++
.../expectedMrssFormat/feed.emptyItems.xml | 19 +++
.../expectedMrssFormat/feed.microblog.xml | 26 ++++
6 files changed, 300 insertions(+), 45 deletions(-)
create mode 100644 tests/MrssFormatTest.php
create mode 100644 tests/samples/expectedMrssFormat/feed.common.xml
create mode 100644 tests/samples/expectedMrssFormat/feed.empty.xml
create mode 100644 tests/samples/expectedMrssFormat/feed.emptyItems.xml
create mode 100644 tests/samples/expectedMrssFormat/feed.microblog.xml
diff --git a/formats/MrssFormat.php b/formats/MrssFormat.php
index 34b9a92a..836a361a 100644
--- a/formats/MrssFormat.php
+++ b/formats/MrssFormat.php
@@ -1,18 +1,45 @@
xml_encode($_SERVER['REQUEST_URI']) : '';
+ public function stringify(){
+ $urlPrefix = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
+ $urlHost = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : '';
+ $urlPath = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : '';
+ $urlRequest = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : '';
+
+ $feedUrl = $this->xml_encode($urlPrefix . $urlHost . $urlRequest);
$extraInfos = $this->getExtraInfos();
$title = $this->xml_encode($extraInfos['name']);
+ $icon = $extraInfos['icon'];
if(!empty($extraInfos['uri'])) {
$uri = $this->xml_encode($extraInfos['uri']);
@@ -20,34 +47,48 @@ class MrssFormat extends FormatAbstract {
$uri = REPOSITORY;
}
- $uriparts = parse_url($uri);
- $icon = $this->xml_encode($uriparts['scheme'] . '://' . $uriparts['host'] . '/favicon.ico');
-
$items = '';
foreach($this->getItems() as $item) {
- $itemAuthor = $this->xml_encode($item->getAuthor());
+ $itemTimestamp = $item->getTimestamp();
$itemTitle = $this->xml_encode($item->getTitle());
$itemUri = $this->xml_encode($item->getURI());
- $itemTimestamp = $this->xml_encode(date(DATE_RFC2822, $item->getTimestamp()));
$itemContent = $this->xml_encode($this->sanitizeHtml($item->getContent()));
+ $entryID = $item->getUid();
+ $isPermaLink = 'false';
+
+ if (empty($entryID) && !empty($itemUri)) { // Fallback to provided URI
+ $entryID = $itemUri;
+ $isPermaLink = 'true';
+ }
+
+ if (empty($entryID)) // Fallback to title and content
+ $entryID = hash('sha1', $itemTitle . $itemContent);
+
+ $entryTitle = '';
+ if (!empty($itemTitle))
+ $entryTitle = '' . $itemTitle . '';
+
+ $entryLink = '';
+ if (!empty($itemUri))
+ $entryLink = '' . $itemUri . '';
+
+ $entryPublished = '';
+ if (!empty($itemTimestamp)) {
+ $entryPublished = ''
+ . $this->xml_encode(gmdate(DATE_RFC2822, $itemTimestamp))
+ . '';
+ }
+
+ $entryDescription = '';
+ if (!empty($itemContent))
+ $entryDescription = '' . $itemContent . '';
- $entryEnclosuresWarning = '';
$entryEnclosures = '';
- if(!empty($item->getEnclosures())) {
- $entryEnclosures .= '';
-
- if(count($item->getEnclosures()) > 1) {
- $entryEnclosures .= PHP_EOL;
- $entryEnclosuresWarning = '<br>Warning:
-Some media files might not be shown to you. Consider using the ATOM format instead!';
- foreach($item->getEnclosures() as $enclosure) {
- $entryEnclosures .= ''
- . PHP_EOL;
- }
- }
+ foreach($item->getEnclosures() as $enclosure) {
+ $entryEnclosures .= ''
+ . PHP_EOL;
}
$entryCategories = '';
@@ -60,12 +101,11 @@ Some media files might not be shown to you. Consider using the ATOM format inste
$items .= <<
- {$itemTitle}
- {$itemUri}
- {$itemUri}
- {$itemTimestamp}
- {$itemContent}{$entryEnclosuresWarning}
- {$itemAuthor}
+ {$entryTitle}
+ {$entryLink}
+ {$entryID}
+ {$entryPublished}
+ {$entryDescription}
{$entryEnclosures}
{$entryCategories}
@@ -75,22 +115,28 @@ EOD;
$charset = $this->getCharset();
- /* xml attributes need to have certain characters escaped to be w3c compliant */
- $imageTitle = htmlspecialchars($title, ENT_COMPAT);
+ $feedImage = '';
+ if (!empty($icon) && in_array(substr($icon, -4), self::ALLOWED_IMAGE_EXT)) {
+ $feedImage .= <<
+ {$icon}
+ {$title}
+ {$uri}
+
+EOD;
+ }
+
/* Data are prepared, now let's begin the "MAGIE !!!" */
$toReturn = <<
-
+
{$title}
- http{$https}://{$httpHost}{$httpInfo}/
+ {$uri}
{$title}
-
-
-
+ {$feedImage}
+
+
{$items}
diff --git a/tests/MrssFormatTest.php b/tests/MrssFormatTest.php
new file mode 100644
index 00000000..b4dd32a9
--- /dev/null
+++ b/tests/MrssFormatTest.php
@@ -0,0 +1,90 @@
+setSample($path);
+ $this->initFormat();
+
+ $this->assertContains(
+ 'Content-Type: application/rss+xml; charset=' . $this->format->getCharset(),
+ xdebug_get_headers()
+ );
+ }
+
+ /**
+ * @dataProvider sampleProvider
+ * @runInSeparateProcess
+ */
+ public function testOutput($path) {
+ $this->setSample($path);
+ $this->initFormat();
+
+ $this->assertXmlStringEqualsXmlFile($this->sample->expected, $this->data);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function sampleProvider() {
+ $samples = array();
+ foreach (glob(self::PATH_SAMPLES . '*.json') as $path) {
+ $samples[basename($path, '.json')] = array($path);
+ }
+ return $samples;
+ }
+
+ private function setSample($path) {
+ $data = json_decode(file_get_contents($path), true);
+ if (isset($data['meta']) && isset($data['items'])) {
+ if (!empty($data['server']))
+ $this->setServerVars($data['server']);
+
+ $items = array();
+ foreach($data['items'] as $item) {
+ $items[] = new \FeedItem($item);
+ }
+
+ $this->sample = (object)array(
+ 'meta' => $data['meta'],
+ 'items' => $items,
+ 'expected' => self::PATH_EXPECTED . basename($path, '.json') . '.xml'
+ );
+ } else {
+ $this->fail('invalid test sample: ' . basename($path, '.json'));
+ }
+ }
+
+ private function setServerVars($list) {
+ $_SERVER = array_merge($_SERVER, $list);
+ }
+
+ private function initFormat() {
+ $this->format = \Format::create('Mrss');
+ $this->format->setItems($this->sample->items);
+ $this->format->setExtraInfos($this->sample->meta);
+ $this->format->setLastModified(strtotime('2000-01-01 12:00:00 UTC'));
+
+ $this->data = $this->getActualOutput($this->format->display());
+ $this->assertNotFalse(simplexml_load_string($this->data));
+ ob_clean();
+ }
+}
diff --git a/tests/samples/expectedMrssFormat/feed.common.xml b/tests/samples/expectedMrssFormat/feed.common.xml
new file mode 100644
index 00000000..38a16f88
--- /dev/null
+++ b/tests/samples/expectedMrssFormat/feed.common.xml
@@ -0,0 +1,64 @@
+
+
+
+ Sample feed with common data
+ https://example.com/blog/
+ Sample feed with common data
+
+ https://example.com/logo.png
+ Sample feed with common data
+ https://example.com/blog/
+
+
+
+
+ -
+ Test Entry
+ http://example.com/blog/test-entry
+ http://example.com/blog/test-entry
+ Sat, 01 Dec 2018 12:00:00 +0000
+ Hello world, this is a test entry.
+ test
+ Hello World
+ example
+
+ -
+ Announcing JSON Feed
+ https://jsonfeed.org/2017/05/17/announcing_json_feed
+ https://jsonfeed.org/2017/05/17/announcing_json_feed
+ Wed, 17 May 2017 13:02:12 +0000
+ <p>We — Manton Reece and Brent Simmons — have noticed that JSON has become the developers’ choice for APIs, and that developers will often go out of their way to avoid XML. JSON is simpler to read and write, and it’s less prone to bugs.</p>
+
+<p>So we developed JSON Feed, a format similar to <a href="http://cyber.harvard.edu/rss/rss.html">RSS</a> and <a href="https://tools.ietf.org/html/rfc4287">Atom</a> but in JSON. It reflects the lessons learned from our years of work reading and publishing feeds.</p>
+
+<p><a href="https://jsonfeed.org/version/1">See the spec</a>. It’s at version 1, which may be the only version ever needed. If future versions are needed, version 1 feeds will still be valid feeds.</p>
+
+<h4>Notes</h4>
+
+<p>We have a <a href="https://github.com/manton/jsonfeed-wp">WordPress plugin</a> and, coming soon, a JSON Feed Parser for Swift. As more code is written, by us and others, we’ll update the <a href="https://jsonfeed.org/code">code</a> page.</p>
+
+<p>See <a href="https://jsonfeed.org/mappingrssandatom">Mapping RSS and Atom to JSON Feed</a> for more on the similarities between the formats.</p>
+
+<p>This website — the Markdown files and supporting resources — <a href="https://github.com/brentsimmons/JSONFeed">is up on GitHub</a>, and you’re welcome to comment there.</p>
+
+<p>This website is also a blog, and you can subscribe to the <a href="https://jsonfeed.org/xml/rss.xml">RSS feed</a> or the <a href="https://jsonfeed.org/feed.json">JSON feed</a> (if your reader supports it).</p>
+
+<p>We worked with a number of people on this over the course of several months. We list them, and thank them, at the bottom of the <a href="https://jsonfeed.org/version/1">spec</a>. But — most importantly — <a href="http://furbo.org/">Craig Hockenberry</a> spent a little time making it look pretty. :)</p>
+
+ -
+ Atom draft-07 snapshot
+ http://example.org/2005/04/02/atom
+ dd6b6c920d3b340ab9e07faf6682f2a7c4f70134
+ Sun, 31 Jul 2005 12:29:29 +0000
+ <p><i>[Update: The Atom draft is finished.]</i></p>
+
+
+ -
+ Star City
+ http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp
+ http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp
+ Tue, 03 Jun 2003 09:39:21 +0000
+ How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's <a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm">Star City</a>.
+
+
+
diff --git a/tests/samples/expectedMrssFormat/feed.empty.xml b/tests/samples/expectedMrssFormat/feed.empty.xml
new file mode 100644
index 00000000..888c42b6
--- /dev/null
+++ b/tests/samples/expectedMrssFormat/feed.empty.xml
@@ -0,0 +1,10 @@
+
+
+
+ Sample feed with minimum data
+ https://github.com/RSS-Bridge/rss-bridge/
+ Sample feed with minimum data
+
+
+
+
diff --git a/tests/samples/expectedMrssFormat/feed.emptyItems.xml b/tests/samples/expectedMrssFormat/feed.emptyItems.xml
new file mode 100644
index 00000000..9e712ddd
--- /dev/null
+++ b/tests/samples/expectedMrssFormat/feed.emptyItems.xml
@@ -0,0 +1,19 @@
+
+
+
+ Sample feed with minimum data
+ https://github.com/RSS-Bridge/rss-bridge/
+ Sample feed with minimum data
+
+
+
+ -
+ Sample Item #1
+ 29f59918d266c56a935da13e4122b524298e5a39
+
+ -
+ Sample Item #2
+ edf358cad1a7ae255d6bc97640dd9d27738f1b7b
+
+
+
diff --git a/tests/samples/expectedMrssFormat/feed.microblog.xml b/tests/samples/expectedMrssFormat/feed.microblog.xml
new file mode 100644
index 00000000..81dac87a
--- /dev/null
+++ b/tests/samples/expectedMrssFormat/feed.microblog.xml
@@ -0,0 +1,26 @@
+
+
+
+ Sample microblog feed
+ https://example.com/blog/
+ Sample microblog feed
+
+ https://example.com/logo.png
+ Sample microblog feed
+ https://example.com/blog/
+
+
+
+
+ -
+ 1918f084648b82057c1dd3faa3d091da82a6fac2
+ Sun, 07 Oct 2018 16:53:03 +0000
+ Oh 😲 I found three monkeys 🙈🙉🙊
+
+ -
+ e62189168a06dfa74f61c621c79c33c4c8517e1f
+ Sun, 07 Oct 2018 16:38:17 +0000
+ Something happened
+
+
+
From 29b187fc1223e8c7575b8935d7d2ca219efced40 Mon Sep 17 00:00:00 2001
From: David Pedersen
Date: Wed, 6 Feb 2019 17:43:20 +0100
Subject: [PATCH 13/66] [AppleMusicBridge] Add new bridge (#1026)
---
bridges/AppleMusicBridge.php | 62 ++++++++++++++++++++++++++++++++++++
1 file changed, 62 insertions(+)
create mode 100644 bridges/AppleMusicBridge.php
diff --git a/bridges/AppleMusicBridge.php b/bridges/AppleMusicBridge.php
new file mode 100644
index 00000000..5a4f40a4
--- /dev/null
+++ b/bridges/AppleMusicBridge.php
@@ -0,0 +1,62 @@
+ [
+ 'name' => 'Artist URL',
+ 'exampleValue' => 'https://itunes.apple.com/us/artist/dunderpatrullen/329796274',
+ 'required' => true,
+ ],
+ 'imgSize' => [
+ 'name' => 'Image size for thumbnails (in px)',
+ 'type' => 'number',
+ 'defaultValue' => 512,
+ 'required' => true,
+ ]
+ ]];
+ const CACHE_TIMEOUT = 21600; // 6 hours
+
+ public function collectData() {
+ $url = $this->getInput('url');
+ $html = getSimpleHTMLDOM($url)
+ or returnServerError('Could not request: ' . $url);
+
+ $imgSize = $this->getInput('imgSize');
+
+ // Grab the json data from the page
+ $html = $html->find('script[id=shoebox-ember-data-store]', 0);
+ $html = strstr($html, '{');
+ $html = substr($html, 0, -9);
+ $json = json_decode($html);
+
+ // Loop through each object
+ foreach ($json->included as $obj) {
+ if ($obj->type === 'lockup/album') {
+ $this->items[] = [
+ 'title' => $obj->attributes->artistName . ' - ' . $obj->attributes->name,
+ 'uri' => $obj->attributes->url,
+ 'timestamp' => $obj->attributes->releaseDate,
+ 'enclosures' => $obj->relationships->artwork->data->id,
+ ];
+ } elseif ($obj->type === 'image') {
+ $images[$obj->id] = $obj->attributes->url;
+ }
+ }
+
+ // Add the images to each item
+ foreach ($this->items as &$item) {
+ $item['enclosures'] = [
+ str_replace('{w}x{h}bb.{f}', $imgSize . 'x0w.jpg', $images[$item['enclosures']]),
+ ];
+ }
+
+ // Sort the order to put the latest albums first
+ usort($this->items, function($a, $b){
+ return $a['timestamp'] < $b['timestamp'];
+ });
+ }
+}
From 69cb65c1af7a2f8c2c3f15e2c8e87ec808f7e46e Mon Sep 17 00:00:00 2001
From: Nova
Date: Wed, 6 Feb 2019 12:20:25 -0500
Subject: [PATCH 14/66] [GlowficBridge] Add new bridge (#1031)
---
bridges/GlowficBridge.php | 88 +++++++++++++++++++++++++++++++++++++++
1 file changed, 88 insertions(+)
create mode 100644 bridges/GlowficBridge.php
diff --git a/bridges/GlowficBridge.php b/bridges/GlowficBridge.php
new file mode 100644
index 00000000..e8975a79
--- /dev/null
+++ b/bridges/GlowficBridge.php
@@ -0,0 +1,88 @@
+ array(),
+ 'Thread' => array(
+ 'post_id' => array(
+ 'name' => 'Post ID',
+ 'title' => 'https://www.glowfic.com/posts/',
+ 'type' => 'number'
+ ),
+ 'start_page' => array(
+ 'name' => 'Start Page',
+ 'title' => 'To start from an offset page',
+ 'type' => 'number'
+ )
+ )
+ );
+
+ public function collectData() {
+ $url = $this->getAPIURI();
+ $metadata = get_headers( $url . '/replies', true ) or returnClientError('Post did not return reply headers.');
+ $metadata['Last-Page'] = ceil( $metadata['Total'] / $metadata['Per-Page'] );
+ if(!is_null($this->getInput('start_page')) &&
+ $this->getInput('start_page') < 1 && $metadata['Last-Page'] - $this->getInput('start_page') > 0) {
+ $first_page = $metadata['Last-Page'] - $this->getInput('start_page');
+ } else if(!is_null($this->getInput('start_page')) && $this->getInput('start_page') <= $metadata['Last-Page']) {
+ $first_page = $this->getInput('start_page');
+ } else {
+ $first_page = 1;
+ }
+ for ($page_offset = $first_page; $page_offset <= $metadata['Last-Page']; $page_offset++) {
+ $jsonContents = getContents($url . '/replies?page=' . $page_offset ) or
+ returnClientError('Could not retrieve replies for page ' . $page_offset . '.');
+ $replies = json_decode($jsonContents);
+ foreach ($replies as $reply) {
+ $item = array();
+
+ $item['content'] = $reply->{'content'};
+ $item['uri'] = $this->getURI() . '?page=' . $page_offset . '#reply-' . $reply->{'id'};
+ if ($reply->{'icon'}) {
+ $item['enclosures'] = array($reply->{'icon'}->{'url'});
+ }
+ $item['author'] = $reply->{'character'}->{'screenname'} . ' (' . $reply->{'character'}->{'name'} . ')';
+ $item['timestamp'] = date('r', strtotime($reply->{'created_at'}));
+ $item['title'] = 'Tag by ' . $reply->{'user'}->{'username'} . ' updated at ' . $reply->{'updated_at'};
+ $this->items[] = $item;
+ }
+ }
+ }
+
+ private function getAPIURI() {
+ $url = parent::getURI() . '/api/v1/posts/' . $this->getInput('post_id');
+ return $url;
+ }
+
+ public function getURI() {
+ $url = parent::getURI() . '/posts/' . $this->getInput('post_id');
+ return $url;
+ }
+
+ private function getPost() {
+ $url = $this->getAPIURI();
+ $jsonPost = getContents( $url ) or returnClientError('Could not retrieve post metadata.');
+ $post = json_decode($jsonPost);
+ return $post;
+ }
+
+ public function getName(){
+ if(!is_null($this->getInput('post_id'))) {
+ $post = $this->getPost();
+ return $post->{'subject'} . ' - ' . parent::getName();
+ }
+ return parent::getName();
+ }
+
+ public function getDescription(){
+ if(!is_null($this->getInput('post_id'))) {
+ $post = $this->getPost();
+ return $post->{'content'};
+ }
+ return parent::getName();
+ }
+}
From 51ee541d5ab562be51d098840df06aeeb6164812 Mon Sep 17 00:00:00 2001
From: LogMANOriginal
Date: Wed, 6 Feb 2019 18:34:51 +0100
Subject: [PATCH 15/66] core: Implement action factory (#1002)
---
actions/DetectAction.php | 50 +++++
actions/DisplayAction.php | 234 +++++++++++++++++++++++
actions/ListAction.php | 53 ++++++
index.php | 286 +----------------------------
lib/ActionAbstract.php | 33 ++++
lib/ActionFactory.php | 65 +++++++
lib/ActionInterface.php | 34 ++++
lib/FactoryAbstract.php | 70 +++++++
lib/rssbridge.php | 7 +
phpunit.xml | 3 +
tests/ActionImplementationTest.php | 59 ++++++
tests/ListActionTest.php | 90 +++++++++
12 files changed, 705 insertions(+), 279 deletions(-)
create mode 100644 actions/DetectAction.php
create mode 100644 actions/DisplayAction.php
create mode 100644 actions/ListAction.php
create mode 100644 lib/ActionAbstract.php
create mode 100644 lib/ActionFactory.php
create mode 100644 lib/ActionInterface.php
create mode 100644 lib/FactoryAbstract.php
create mode 100644 tests/ActionImplementationTest.php
create mode 100644 tests/ListActionTest.php
diff --git a/actions/DetectAction.php b/actions/DetectAction.php
new file mode 100644
index 00000000..2ad79a27
--- /dev/null
+++ b/actions/DetectAction.php
@@ -0,0 +1,50 @@
+userData['url']
+ or returnClientError('You must specify a url!');
+
+ $format = $this->userData['format']
+ or returnClientError('You must specify a format!');
+
+ foreach(Bridge::getBridgeNames() as $bridgeName) {
+
+ if(!Bridge::isWhitelisted($bridgeName)) {
+ continue;
+ }
+
+ $bridge = Bridge::create($bridgeName);
+
+ if($bridge === false) {
+ continue;
+ }
+
+ $bridgeParams = $bridge->detectParameters($targetURL);
+
+ if(is_null($bridgeParams)) {
+ continue;
+ }
+
+ $bridgeParams['bridge'] = $bridgeName;
+ $bridgeParams['format'] = $format;
+
+ header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301);
+ die();
+
+ }
+
+ returnClientError('No bridge found for given URL: ' . $targetURL);
+ }
+}
diff --git a/actions/DisplayAction.php b/actions/DisplayAction.php
new file mode 100644
index 00000000..6b599329
--- /dev/null
+++ b/actions/DisplayAction.php
@@ -0,0 +1,234 @@
+userData) ? $this->userData['bridge'] : null;
+
+ $format = $this->userData['format']
+ or returnClientError('You must specify a format!');
+
+ // DEPRECATED: 'nameFormat' scheme is replaced by 'name' in format parameter values
+ // this is to keep compatibility until futher complete removal
+ if(($pos = strpos($format, 'Format')) === (strlen($format) - strlen('Format'))) {
+ $format = substr($format, 0, $pos);
+ }
+
+ // whitelist control
+ if(!Bridge::isWhitelisted($bridge)) {
+ throw new \Exception('This bridge is not whitelisted', 401);
+ die;
+ }
+
+ // Data retrieval
+ $bridge = Bridge::create($bridge);
+
+ $noproxy = array_key_exists('_noproxy', $this->userData)
+ && filter_var($this->userData['_noproxy'], FILTER_VALIDATE_BOOLEAN);
+
+ if(defined('PROXY_URL') && PROXY_BYBRIDGE && $noproxy) {
+ define('NOPROXY', true);
+ }
+
+ // Cache timeout
+ $cache_timeout = -1;
+ if(array_key_exists('_cache_timeout', $this->userData)) {
+
+ if(!CUSTOM_CACHE_TIMEOUT) {
+ unset($this->userData['_cache_timeout']);
+ $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($this->userData);
+ header('Location: ' . $uri, true, 301);
+ die();
+ }
+
+ $cache_timeout = filter_var($this->userData['_cache_timeout'], FILTER_VALIDATE_INT);
+
+ } else {
+ $cache_timeout = $bridge->getCacheTimeout();
+ }
+
+ // Remove parameters that don't concern bridges
+ $bridge_params = array_diff_key(
+ $this->userData,
+ array_fill_keys(
+ array(
+ 'action',
+ 'bridge',
+ 'format',
+ '_noproxy',
+ '_cache_timeout',
+ '_error_time'
+ ), '')
+ );
+
+ // Remove parameters that don't concern caches
+ $cache_params = array_diff_key(
+ $this->userData,
+ array_fill_keys(
+ array(
+ 'action',
+ 'format',
+ '_noproxy',
+ '_cache_timeout',
+ '_error_time'
+ ), '')
+ );
+
+ // Initialize cache
+ $cache = Cache::create('FileCache');
+ $cache->setPath(PATH_CACHE);
+ $cache->purgeCache(86400); // 24 hours
+ $cache->setParameters($cache_params);
+
+ $items = array();
+ $infos = array();
+ $mtime = $cache->getTime();
+
+ if($mtime !== false
+ && (time() - $cache_timeout < $mtime)
+ && !Debug::isEnabled()) { // Load cached data
+
+ // Send "Not Modified" response if client supports it
+ // Implementation based on https://stackoverflow.com/a/10847262
+ if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
+ $stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
+
+ if($mtime <= $stime) { // Cached data is older or same
+ header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304);
+ die();
+ }
+ }
+
+ $cached = $cache->loadData();
+
+ if(isset($cached['items']) && isset($cached['extraInfos'])) {
+ foreach($cached['items'] as $item) {
+ $items[] = new \FeedItem($item);
+ }
+
+ $infos = $cached['extraInfos'];
+ }
+
+ } else { // Collect new data
+
+ try {
+ $bridge->setDatas($bridge_params);
+ $bridge->collectData();
+
+ $items = $bridge->getItems();
+
+ // Transform "legacy" items to FeedItems if necessary.
+ // Remove this code when support for "legacy" items ends!
+ if(isset($items[0]) && is_array($items[0])) {
+ $feedItems = array();
+
+ foreach($items as $item) {
+ $feedItems[] = new \FeedItem($item);
+ }
+
+ $items = $feedItems;
+ }
+
+ $infos = array(
+ 'name' => $bridge->getName(),
+ 'uri' => $bridge->getURI(),
+ 'icon' => $bridge->getIcon()
+ );
+ } catch(Error $e) {
+ error_log($e);
+
+ $item = new \FeedItem();
+
+ // Create "new" error message every 24 hours
+ $this->userData['_error_time'] = urlencode((int)(time() / 86400));
+
+ // Error 0 is a special case (i.e. "trying to get property of non-object")
+ if($e->getCode() === 0) {
+ $item->setTitle(
+ 'Bridge encountered an unexpected situation! ('
+ . $this->userData['_error_time']
+ . ')'
+ );
+ } else {
+ $item->setTitle(
+ 'Bridge returned error '
+ . $e->getCode()
+ . '! ('
+ . $this->userData['_error_time']
+ . ')'
+ );
+ }
+
+ $item->setURI(
+ (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
+ . '?'
+ . http_build_query($this->userData)
+ );
+
+ $item->setTimestamp(time());
+ $item->setContent(buildBridgeException($e, $bridge));
+
+ $items[] = $item;
+ } catch(Exception $e) {
+ error_log($e);
+
+ $item = new \FeedItem();
+
+ // Create "new" error message every 24 hours
+ $this->userData['_error_time'] = urlencode((int)(time() / 86400));
+
+ $item->setURI(
+ (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
+ . '?'
+ . http_build_query($this->userData)
+ );
+
+ $item->setTitle(
+ 'Bridge returned error '
+ . $e->getCode()
+ . '! ('
+ . $this->userData['_error_time']
+ . ')'
+ );
+ $item->setTimestamp(time());
+ $item->setContent(buildBridgeException($e, $bridge));
+
+ $items[] = $item;
+ }
+
+ // Store data in cache
+ $cache->saveData(array(
+ 'items' => array_map(function($i){ return $i->toArray(); }, $items),
+ 'extraInfos' => $infos
+ ));
+
+ }
+
+ // Data transformation
+ try {
+ $format = Format::create($format);
+ $format->setItems($items);
+ $format->setExtraInfos($infos);
+ $format->setLastModified($cache->getTime());
+ $format->display();
+ } catch(Error $e) {
+ error_log($e);
+ header('Content-Type: text/html', true, $e->getCode());
+ die(buildTransformException($e, $bridge));
+ } catch(Exception $e) {
+ error_log($e);
+ header('Content-Type: text/html', true, $e->getCode());
+ die(buildTransformException($e, $bridge));
+ }
+ }
+}
diff --git a/actions/ListAction.php b/actions/ListAction.php
new file mode 100644
index 00000000..03e06119
--- /dev/null
+++ b/actions/ListAction.php
@@ -0,0 +1,53 @@
+bridges = array();
+ $list->total = 0;
+
+ foreach(Bridge::getBridgeNames() as $bridgeName) {
+
+ $bridge = Bridge::create($bridgeName);
+
+ if($bridge === false) { // Broken bridge, show as inactive
+
+ $list->bridges[$bridgeName] = array(
+ 'status' => 'inactive'
+ );
+
+ continue;
+
+ }
+
+ $status = Bridge::isWhitelisted($bridgeName) ? 'active' : 'inactive';
+
+ $list->bridges[$bridgeName] = array(
+ 'status' => $status,
+ 'uri' => $bridge->getURI(),
+ 'name' => $bridge->getName(),
+ 'icon' => $bridge->getIcon(),
+ 'parameters' => $bridge->getParameters(),
+ 'maintainer' => $bridge->getMaintainer(),
+ 'description' => $bridge->getDescription()
+ );
+
+ }
+
+ $list->total = count($list->bridges);
+
+ header('Content-Type: application/json');
+ echo json_encode($list, JSON_PRETTY_PRINT);
+ }
+}
diff --git a/index.php b/index.php
index a95302a0..819b5a52 100644
--- a/index.php
+++ b/index.php
@@ -51,287 +51,15 @@ $whitelist_default = array(
try {
Bridge::setWhitelist($whitelist_default);
+ $actionFac = new \ActionFactory();
+ $actionFac->setWorkingDir(PATH_LIB_ACTIONS);
- $showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN);
- $action = array_key_exists('action', $params) ? $params['action'] : null;
- $bridge = array_key_exists('bridge', $params) ? $params['bridge'] : null;
-
- // Return list of bridges as JSON formatted text
- if($action === 'list') {
-
- $list = new StdClass();
- $list->bridges = array();
- $list->total = 0;
-
- foreach(Bridge::getBridgeNames() as $bridgeName) {
-
- $bridge = Bridge::create($bridgeName);
-
- if($bridge === false) { // Broken bridge, show as inactive
-
- $list->bridges[$bridgeName] = array(
- 'status' => 'inactive'
- );
-
- continue;
-
- }
-
- $status = Bridge::isWhitelisted($bridgeName) ? 'active' : 'inactive';
-
- $list->bridges[$bridgeName] = array(
- 'status' => $status,
- 'uri' => $bridge->getURI(),
- 'name' => $bridge->getName(),
- 'icon' => $bridge->getIcon(),
- 'parameters' => $bridge->getParameters(),
- 'maintainer' => $bridge->getMaintainer(),
- 'description' => $bridge->getDescription()
- );
-
- }
-
- $list->total = count($list->bridges);
-
- header('Content-Type: application/json');
- echo json_encode($list, JSON_PRETTY_PRINT);
-
- } elseif($action === 'detect') {
-
- $targetURL = $params['url']
- or returnClientError('You must specify a url!');
-
- $format = $params['format']
- or returnClientError('You must specify a format!');
-
- foreach(Bridge::getBridgeNames() as $bridgeName) {
-
- if(!Bridge::isWhitelisted($bridgeName)) {
- continue;
- }
-
- $bridge = Bridge::create($bridgeName);
-
- if($bridge === false) {
- continue;
- }
-
- $bridgeParams = $bridge->detectParameters($targetURL);
-
- if(is_null($bridgeParams)) {
- continue;
- }
-
- $bridgeParams['bridge'] = $bridgeName;
- $bridgeParams['format'] = $format;
-
- header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301);
- die();
-
- }
-
- returnClientError('No bridge found for given URL: ' . $targetURL);
-
- } elseif($action === 'display' && !empty($bridge)) {
-
- $format = $params['format']
- or returnClientError('You must specify a format!');
-
- // DEPRECATED: 'nameFormat' scheme is replaced by 'name' in format parameter values
- // this is to keep compatibility until futher complete removal
- if(($pos = strpos($format, 'Format')) === (strlen($format) - strlen('Format'))) {
- $format = substr($format, 0, $pos);
- }
-
- // whitelist control
- if(!Bridge::isWhitelisted($bridge)) {
- throw new \Exception('This bridge is not whitelisted', 401);
- die;
- }
-
- // Data retrieval
- $bridge = Bridge::create($bridge);
-
- $noproxy = array_key_exists('_noproxy', $params) && filter_var($params['_noproxy'], FILTER_VALIDATE_BOOLEAN);
- if(defined('PROXY_URL') && PROXY_BYBRIDGE && $noproxy) {
- define('NOPROXY', true);
- }
-
- // Cache timeout
- $cache_timeout = -1;
- if(array_key_exists('_cache_timeout', $params)) {
-
- if(!CUSTOM_CACHE_TIMEOUT) {
- unset($params['_cache_timeout']);
- $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
- header('Location: ' . $uri, true, 301);
- die();
- }
-
- $cache_timeout = filter_var($params['_cache_timeout'], FILTER_VALIDATE_INT);
-
- } else {
- $cache_timeout = $bridge->getCacheTimeout();
- }
-
- // Remove parameters that don't concern bridges
- $bridge_params = array_diff_key(
- $params,
- array_fill_keys(
- array(
- 'action',
- 'bridge',
- 'format',
- '_noproxy',
- '_cache_timeout',
- '_error_time'
- ), '')
- );
-
- // Remove parameters that don't concern caches
- $cache_params = array_diff_key(
- $params,
- array_fill_keys(
- array(
- 'action',
- 'format',
- '_noproxy',
- '_cache_timeout',
- '_error_time'
- ), '')
- );
-
- // Initialize cache
- $cache = Cache::create('FileCache');
- $cache->setPath(PATH_CACHE);
- $cache->purgeCache(86400); // 24 hours
- $cache->setParameters($cache_params);
-
- $items = array();
- $infos = array();
- $mtime = $cache->getTime();
-
- if($mtime !== false
- && (time() - $cache_timeout < $mtime)
- && !Debug::isEnabled()) { // Load cached data
-
- // Send "Not Modified" response if client supports it
- // Implementation based on https://stackoverflow.com/a/10847262
- if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
- $stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
-
- if($mtime <= $stime) { // Cached data is older or same
- header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304);
- die();
- }
- }
-
- $cached = $cache->loadData();
-
- if(isset($cached['items']) && isset($cached['extraInfos'])) {
- foreach($cached['items'] as $item) {
- $items[] = new \FeedItem($item);
- }
-
- $infos = $cached['extraInfos'];
- }
-
- } else { // Collect new data
-
- try {
- $bridge->setDatas($bridge_params);
- $bridge->collectData();
-
- $items = $bridge->getItems();
-
- // Transform "legacy" items to FeedItems if necessary.
- // Remove this code when support for "legacy" items ends!
- if(isset($items[0]) && is_array($items[0])) {
- $feedItems = array();
-
- foreach($items as $item) {
- $feedItems[] = new \FeedItem($item);
- }
-
- $items = $feedItems;
- }
-
- $infos = array(
- 'name' => $bridge->getName(),
- 'uri' => $bridge->getURI(),
- 'icon' => $bridge->getIcon()
- );
- } catch(Error $e) {
- error_log($e);
-
- $item = new \FeedItem();
-
- // Create "new" error message every 24 hours
- $params['_error_time'] = urlencode((int)(time() / 86400));
-
- // Error 0 is a special case (i.e. "trying to get property of non-object")
- if($e->getCode() === 0) {
- $item->setTitle('Bridge encountered an unexpected situation! (' . $params['_error_time'] . ')');
- } else {
- $item->setTitle('Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')');
- }
-
- $item->setURI(
- (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
- . '?'
- . http_build_query($params)
- );
-
- $item->setTimestamp(time());
- $item->setContent(buildBridgeException($e, $bridge));
-
- $items[] = $item;
- } catch(Exception $e) {
- error_log($e);
-
- $item = new \FeedItem();
-
- // Create "new" error message every 24 hours
- $params['_error_time'] = urlencode((int)(time() / 86400));
-
- $item->setURI(
- (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
- . '?'
- . http_build_query($params)
- );
-
- $item->setTitle('Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')');
- $item->setTimestamp(time());
- $item->setContent(buildBridgeException($e, $bridge));
-
- $items[] = $item;
- }
-
- // Store data in cache
- $cache->saveData(array(
- 'items' => array_map(function($i){ return $i->toArray(); }, $items),
- 'extraInfos' => $infos
- ));
-
- }
-
- // Data transformation
- try {
- $format = Format::create($format);
- $format->setItems($items);
- $format->setExtraInfos($infos);
- $format->setLastModified($cache->getTime());
- $format->display();
- } catch(Error $e) {
- error_log($e);
- header('Content-Type: text/html', true, $e->getCode());
- die(buildTransformException($e, $bridge));
- } catch(Exception $e) {
- error_log($e);
- header('Content-Type: text/html', true, $e->getCode());
- die(buildTransformException($e, $bridge));
- }
+ if(array_key_exists('action', $params)) {
+ $action = $actionFac->create($params['action']);
+ $action->setUserData($params);
+ $action->execute();
} else {
+ $showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN);
echo BridgeList::create($showInactive);
}
} catch(\Exception $e) {
diff --git a/lib/ActionAbstract.php b/lib/ActionAbstract.php
new file mode 100644
index 00000000..b925d609
--- /dev/null
+++ b/lib/ActionAbstract.php
@@ -0,0 +1,33 @@
+userData = $userData;
+ }
+}
diff --git a/lib/ActionFactory.php b/lib/ActionFactory.php
new file mode 100644
index 00000000..8146e542
--- /dev/null
+++ b/lib/ActionFactory.php
@@ -0,0 +1,65 @@
+buildFilePath($name);
+
+ if(!file_exists($filePath)) {
+ throw new \Exception('File ' . $filePath . ' does not exist!');
+ }
+
+ require_once $filePath;
+
+ $class = $this->buildClassName($name);
+
+ if((new \ReflectionClass($class))->isInstantiable()) {
+ return new $class();
+ }
+
+ return false;
+ }
+
+ /**
+ * Build class name from action name
+ *
+ * The class name consists of the action name with prefix "Action". The first
+ * character of the class name must be uppercase.
+ *
+ * Example: 'display' => 'DisplayAction'
+ *
+ * @param string $name The action name.
+ * @return string The class name.
+ */
+ protected function buildClassName($name) {
+ return ucfirst(strtolower($name)) . 'Action';
+ }
+
+ /**
+ * Build file path to the action class.
+ *
+ * @param string $name The action name.
+ * @return string Path to the action class.
+ */
+ protected function buildFilePath($name) {
+ return $this->getWorkingDir() . $this->buildClassName($name) . '.php';
+ }
+}
diff --git a/lib/ActionInterface.php b/lib/ActionInterface.php
new file mode 100644
index 00000000..c38d057a
--- /dev/null
+++ b/lib/ActionInterface.php
@@ -0,0 +1,34 @@
+workingDir = null;
+
+ if(!is_string($dir)) {
+ throw new \InvalidArgumentException('Working directory must be a string!');
+ }
+
+ if(!file_exists($dir)) {
+ throw new \Exception('Working directory does not exist!');
+ }
+
+ if(!is_dir($dir)) {
+ throw new \InvalidArgumentException($dir . ' is not a directory!');
+ }
+
+ $this->workingDir = realpath($dir) . '/';
+ }
+
+ /**
+ * Get the working directory
+ *
+ * @return string The working directory.
+ */
+ public function getWorkingDir() {
+ if(is_null($this->workingDir)) {
+ throw new \LogicException('Working directory is not set!');
+ }
+
+ return $this->workingDir;
+ }
+
+ /**
+ * Creates a new instance for the object specified by name.
+ *
+ * @param string $name The name of the object to create.
+ * @return object The object instance
+ */
+ abstract public function create($name);
+}
diff --git a/lib/rssbridge.php b/lib/rssbridge.php
index bc8c8d04..5a523588 100644
--- a/lib/rssbridge.php
+++ b/lib/rssbridge.php
@@ -29,6 +29,9 @@ define('PATH_LIB_FORMATS', __DIR__ . '/../formats/');
/** Path to the caches library */
define('PATH_LIB_CACHES', __DIR__ . '/../caches/');
+/** Path to the actions library */
+define('PATH_LIB_ACTIONS', __DIR__ . '/../actions/');
+
/** Path to the cache folder */
define('PATH_CACHE', __DIR__ . '/../cache/');
@@ -39,11 +42,13 @@ define('WHITELIST', __DIR__ . '/../whitelist.txt');
define('REPOSITORY', 'https://github.com/RSS-Bridge/rss-bridge/');
// Interfaces
+require_once PATH_LIB . 'ActionInterface.php';
require_once PATH_LIB . 'BridgeInterface.php';
require_once PATH_LIB . 'CacheInterface.php';
require_once PATH_LIB . 'FormatInterface.php';
// Classes
+require_once PATH_LIB . 'FactoryAbstract.php';
require_once PATH_LIB . 'FeedItem.php';
require_once PATH_LIB . 'Debug.php';
require_once PATH_LIB . 'Exceptions.php';
@@ -58,6 +63,8 @@ require_once PATH_LIB . 'Configuration.php';
require_once PATH_LIB . 'BridgeCard.php';
require_once PATH_LIB . 'BridgeList.php';
require_once PATH_LIB . 'ParameterValidator.php';
+require_once PATH_LIB . 'ActionFactory.php';
+require_once PATH_LIB . 'ActionAbstract.php';
// Functions
require_once PATH_LIB . 'html.php';
diff --git a/phpunit.xml b/phpunit.xml
index 4fe1ae0e..62937c47 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -14,6 +14,9 @@
tests
+
+ tests
+
diff --git a/tests/ActionImplementationTest.php b/tests/ActionImplementationTest.php
new file mode 100644
index 00000000..554432f3
--- /dev/null
+++ b/tests/ActionImplementationTest.php
@@ -0,0 +1,59 @@
+setAction($path);
+ $this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character');
+ $this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces');
+ $this->assertStringEndsWith('Action', $this->class, 'class name must end with "Action"');
+ }
+
+ /**
+ * @dataProvider dataActionsProvider
+ */
+ public function testClassType($path) {
+ $this->setAction($path);
+ $this->assertInstanceOf(ActionInterface::class, $this->obj);
+ }
+
+ /**
+ * @dataProvider dataActionsProvider
+ */
+ public function testVisibleMethods($path) {
+ $allowedActionAbstract = get_class_methods(ActionAbstract::class);
+ sort($allowedActionAbstract);
+
+ $this->setAction($path);
+
+ $methods = get_class_methods($this->obj);
+ sort($methods);
+
+ $this->assertEquals($allowedActionAbstract, $methods);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function dataActionsProvider() {
+ $actions = array();
+ foreach (glob(PATH_LIB_ACTIONS . '*.php') as $path) {
+ $actions[basename($path, '.php')] = array($path);
+ }
+ return $actions;
+ }
+
+ private function setAction($path) {
+ require_once $path;
+ $this->class = basename($path, '.php');
+ $this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist');
+ $this->obj = new $this->class();
+ }
+}
diff --git a/tests/ListActionTest.php b/tests/ListActionTest.php
new file mode 100644
index 00000000..7f625882
--- /dev/null
+++ b/tests/ListActionTest.php
@@ -0,0 +1,90 @@
+initAction();
+
+ $this->assertContains(
+ 'Content-Type: application/json',
+ xdebug_get_headers()
+ );
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testOutput() {
+ $this->initAction();
+
+ $items = json_decode($this->data, true);
+
+ $this->assertNotNull($items, 'invalid JSON output: ' . json_last_error_msg());
+
+ $this->assertArrayHasKey('total', $items, 'Missing "total" parameter');
+ $this->assertInternalType('int', $items['total'], 'Invalid type');
+
+ $this->assertArrayHasKey('bridges', $items, 'Missing "bridges" array');
+
+ $this->assertEquals(
+ $items['total'],
+ count($items['bridges']),
+ 'Item count doesn\'t match'
+ );
+
+ $this->assertEquals(
+ count(Bridge::getBridgeNames()),
+ count($items['bridges']),
+ 'Number of bridges doesn\'t match'
+ );
+
+ $expectedKeys = array(
+ 'status',
+ 'uri',
+ 'name',
+ 'icon',
+ 'parameters',
+ 'maintainer',
+ 'description'
+ );
+
+ $allowedStatus = array(
+ 'active',
+ 'inactive'
+ );
+
+ foreach($items['bridges'] as $bridge) {
+ foreach($expectedKeys as $key) {
+ $this->assertArrayHasKey($key, $bridge, 'Missing key "' . $key . '"');
+ }
+
+ $this->assertContains($bridge['status'], $allowedStatus, 'Invalid status value');
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ private function initAction() {
+ $actionFac = new ActionFactory();
+ $actionFac->setWorkingDir(PATH_LIB_ACTIONS);
+
+ $this->action = $actionFac->create('list');
+ $this->action->setUserData(array()); /* no user data required */
+
+ ob_start();
+ $this->action->execute();
+ $this->data = ob_get_contents();
+ ob_clean();
+ ob_end_flush();
+ }
+}
From 556a417dd6bf39b1a4892bc2c935f32b0d710f28 Mon Sep 17 00:00:00 2001
From: logmanoriginal
Date: Wed, 6 Feb 2019 18:52:44 +0100
Subject: [PATCH 16/66] core: Add support for custom cache types via
config.ini.php
This commit adds support for a new parameter which specifies the type
of cache to use for caching. It is specified in config.ini.php:
[cache]
type = "..."
Currently only one type of cache is supported (see /caches). All uses
of 'FileCache' were replaced by this configuration option.
Note: Caching currently depends on files and folders (due to FileCache).
Experience may vary depending on the selected cache type. For now always
check if FileCache is working before testing alternative types.
References #1000
---
actions/DisplayAction.php | 2 +-
bridges/ElloBridge.php | 2 +-
bridges/WordPressPluginUpdateBridge.php | 2 +-
config.default.ini.php | 4 ++
lib/Cache.php | 73 +++++++++++++++++++++++++
lib/Configuration.php | 3 +
lib/contents.php | 4 +-
7 files changed, 85 insertions(+), 5 deletions(-)
diff --git a/actions/DisplayAction.php b/actions/DisplayAction.php
index 6b599329..b223b757 100644
--- a/actions/DisplayAction.php
+++ b/actions/DisplayAction.php
@@ -85,7 +85,7 @@ class DisplayAction extends ActionAbstract {
);
// Initialize cache
- $cache = Cache::create('FileCache');
+ $cache = Cache::create(Configuration::getConfig('cache', 'type'));
$cache->setPath(PATH_CACHE);
$cache->purgeCache(86400); // 24 hours
$cache->setParameters($cache_params);
diff --git a/bridges/ElloBridge.php b/bridges/ElloBridge.php
index 22f5d309..45d33a53 100644
--- a/bridges/ElloBridge.php
+++ b/bridges/ElloBridge.php
@@ -120,7 +120,7 @@ class ElloBridge extends BridgeAbstract {
}
private function getAPIKey() {
- $cache = Cache::create('FileCache');
+ $cache = Cache::create(Configuration::getConfig('cache', 'type'));
$cache->setPath(PATH_CACHE);
$cache->setParameters(['key']);
$key = $cache->loadData();
diff --git a/bridges/WordPressPluginUpdateBridge.php b/bridges/WordPressPluginUpdateBridge.php
index 80b53eac..51ddd5b7 100644
--- a/bridges/WordPressPluginUpdateBridge.php
+++ b/bridges/WordPressPluginUpdateBridge.php
@@ -75,7 +75,7 @@ class WordPressPluginUpdateBridge extends BridgeAbstract {
private function getCachedDate($url){
Debug::log('getting pubdate from url ' . $url . '');
// Initialize cache
- $cache = Cache::create('FileCache');
+ $cache = Cache::create(Configuration::getConfig('cache', 'type'));
$cache->setPath(PATH_CACHE . 'pages/');
$params = [$url];
$cache->setParameters($params);
diff --git a/config.default.ini.php b/config.default.ini.php
index 2d6fca12..394658d6 100644
--- a/config.default.ini.php
+++ b/config.default.ini.php
@@ -6,6 +6,10 @@
[cache]
+; Defines the cache type used by RSS-Bridge
+; "file" = FileCache (default)
+type = "file"
+
; Allow users to specify custom timeout for specific requests.
; true = enabled
; false = disabled (default)
diff --git a/lib/Cache.php b/lib/Cache.php
index a0d2ac77..6826d4cc 100644
--- a/lib/Cache.php
+++ b/lib/Cache.php
@@ -64,6 +64,8 @@ class Cache {
* @return object|bool The cache object or false if the class is not instantiable.
*/
public static function create($name){
+ $name = self::sanitizeCacheName($name) . 'Cache';
+
if(!self::isCacheName($name)) {
throw new \InvalidArgumentException('Cache name invalid!');
}
@@ -137,4 +139,75 @@ class Cache {
public static function isCacheName($name){
return is_string($name) && preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name) === 1;
}
+
+ /**
+ * Returns a list of cache names from the working directory.
+ *
+ * The list is cached internally to allow for successive calls.
+ *
+ * @return array List of cache names
+ */
+ public static function getCacheNames(){
+
+ static $cacheNames = array(); // Initialized on first call
+
+ if(empty($cacheNames)) {
+ $files = scandir(self::getWorkingDir());
+
+ if($files !== false) {
+ foreach($files as $file) {
+ if(preg_match('/^([^.]+)Cache\.php$/U', $file, $out)) {
+ $cacheNames[] = $out[1];
+ }
+ }
+ }
+ }
+
+ return $cacheNames;
+ }
+
+ /**
+ * Returns the sanitized cache name.
+ *
+ * The cache name can be specified in various ways:
+ * * The PHP file name (i.e. `FileCache.php`)
+ * * The PHP file name without file extension (i.e. `FileCache`)
+ * * The cache name (i.e. `file`)
+ *
+ * Casing is ignored (i.e. `FILE` and `fIlE` are the same).
+ *
+ * A cache file matching the given cache name must exist in the working
+ * directory!
+ *
+ * @param string $name The cache name
+ * @return string|null The sanitized cache name if the provided name is
+ * valid, null otherwise.
+ */
+ protected static function sanitizeCacheName($name) {
+
+ if(is_string($name)) {
+
+ // Trim trailing '.php' if exists
+ if(preg_match('/(.+)(?:\.php)/', $name, $matches)) {
+ $name = $matches[1];
+ }
+
+ // Trim trailing 'Cache' if exists
+ if(preg_match('/(.+)(?:Cache)/i', $name, $matches)) {
+ $name = $matches[1];
+ }
+
+ // The name is valid if a corresponding file is found on disk
+ if(in_array(strtolower($name), array_map('strtolower', self::getCacheNames()))) {
+ $index = array_search(strtolower($name), array_map('strtolower', self::getCacheNames()));
+ return self::getCacheNames()[$index];
+ }
+
+ Debug::log('Invalid cache name specified: "' . $name . '"!');
+
+ }
+
+ return null; // Bad parameter
+
+ }
}
diff --git a/lib/Configuration.php b/lib/Configuration.php
index 63144d26..3a2a7bf3 100644
--- a/lib/Configuration.php
+++ b/lib/Configuration.php
@@ -179,6 +179,9 @@ final class Configuration {
/** Name of the proxy server */
define('PROXY_NAME', self::getConfig('proxy', 'name'));
+ if(!is_string(self::getConfig('cache', 'type')))
+ die('Parameter [cache] => "type" is not a valid string! Please check "config.ini.php"!');
+
if(!is_bool(self::getConfig('cache', 'custom_timeout')))
die('Parameter [cache] => "custom_timeout" is not a valid Boolean! Please check "config.ini.php"!');
diff --git a/lib/contents.php b/lib/contents.php
index dc0ca517..976ecbee 100644
--- a/lib/contents.php
+++ b/lib/contents.php
@@ -45,7 +45,7 @@ function getContents($url, $header = array(), $opts = array()){
Debug::log('Reading contents from "' . $url . '"');
// Initialize cache
- $cache = Cache::create('FileCache');
+ $cache = Cache::create(Configuration::getConfig('cache', 'type'));
$cache->setPath(PATH_CACHE . 'server/');
$cache->purgeCache(86400); // 24 hours (forced)
@@ -268,7 +268,7 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
Debug::log('Caching url ' . $url . ', duration ' . $duration);
// Initialize cache
- $cache = Cache::create('FileCache');
+ $cache = Cache::create(Configuration::getConfig('cache', 'type'));
$cache->setPath(PATH_CACHE . 'pages/');
$cache->purgeCache(86400); // 24 hours (forced)
From ca9c2abb608fb923e78aae3efd343f2aecb5fc50 Mon Sep 17 00:00:00 2001
From: ORelio
Date: Mon, 11 Feb 2019 19:07:03 +0100
Subject: [PATCH 17/66] [FeedExpander] Fix item href being used as feed uri
(#1033)
---
lib/FeedExpander.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/FeedExpander.php b/lib/FeedExpander.php
index b669351f..665620a4 100644
--- a/lib/FeedExpander.php
+++ b/lib/FeedExpander.php
@@ -265,7 +265,7 @@ abstract class FeedExpander extends BridgeAbstract {
//When "link" field is present, URL is more reliable than "id" field
if (count($feedItem->link) === 1) {
- $this->uri = (string)$feedItem->link[0]['href'];
+ $item['uri'] = (string)$feedItem->link[0]['href'];
} else {
foreach($feedItem->link as $link) {
if(strtolower($link['rel']) === 'alternate') {
From 4c58768d4d99bc694242234e1c37e8b6d87bd7e1 Mon Sep 17 00:00:00 2001
From: Klimplant
Date: Mon, 11 Feb 2019 21:07:46 +0100
Subject: [PATCH 18/66] [CachetBridge] Add new bridge (#1034)
* Fix issue with CachetAPI Pagination
Fixing issue that only the oldest 20 entries were shown.
_Background:_
_Cachet has a, lets call it odd, system of pagination. On the first page you see the incidents first created, so they are not what you want to see. But on the last page you can have 1 or 20 of the newest incidents. So you have to take the incidents from the last page (call it Pmax) and combine them with the incidents from Pmax - 1._
---
bridges/CachetBridge.php | 134 +++++++++++++++++++++++++++++++++++++++
1 file changed, 134 insertions(+)
create mode 100644 bridges/CachetBridge.php
diff --git a/bridges/CachetBridge.php b/bridges/CachetBridge.php
new file mode 100644
index 00000000..a60b8f73
--- /dev/null
+++ b/bridges/CachetBridge.php
@@ -0,0 +1,134 @@
+ array(
+ 'name' => 'Cachet installation',
+ 'type' => 'text',
+ 'required' => true,
+ 'title' => 'The URL of the Cachet installation',
+ 'exampleValue' => 'https://demo.cachethq.io/',
+ ), 'additional_info' => array(
+ 'name' => 'Additional Timestamps',
+ 'type' => 'checkbox',
+ 'title' => 'Whether to include the given timestamps'
+ )
+ )
+ );
+ const CACHE_TIMEOUT = 300;
+
+ private $componentCache = [];
+
+ public function getURI() {
+ return $this->getInput('host') === null ? 'https://cachethq.io/' : $this->getInput('host');
+ }
+
+ /**
+ * Validates the ping request to the cache API
+ *
+ * @param string $ping
+ * @return boolean
+ */
+ private function validatePing($ping) {
+ $ping = json_decode($ping);
+ if ($ping === null) {
+ return false;
+ }
+ return $ping->data === 'Pong!';
+ }
+
+ /**
+ * Returns the component name of a cachat component
+ *
+ * @param integer $id
+ * @return string
+ */
+ private function getComponentName($id) {
+ if ($id === 0) {
+ return '';
+ }
+ if (array_key_exists($id, $this->componentCache)) {
+ return $this->componentCache[$id];
+ }
+
+ $component = getContents($this->getURI() . '/api/v1/components/' . $id);
+ $component = json_decode($component);
+ if ($component === null) {
+ return '';
+ }
+ return $component->data->name;
+ }
+
+ public function collectData() {
+ $ping = getContents(urljoin($this->getURI(), '/api/v1/ping'));
+ if (!$this->validatePing($ping)) {
+ returnClientError('Provided URI is invalid!');
+ }
+
+ $url = urljoin($this->getURI(), '/api/v1/incidents?sort=id&order=desc');
+ $incidents = getContents($url);
+ $incidents = json_decode($incidents);
+ if ($incidents === null) {
+ returnClientError('/api/v1/incidents returned no valid json');
+ }
+
+ usort($incidents->data, function ($a, $b) {
+ $timeA = strtotime($a->updated_at);
+ $timeB = strtotime($b->updated_at);
+ return $timeA > $timeB ? -1 : 1;
+ });
+
+ foreach ($incidents->data as $incident) {
+
+ if (isset($incident->permalink)) {
+ $permalink = $incident->permalink;
+ } else {
+ $permalink = urljoin($this->getURI(), '/incident/' . $incident->id);
+ }
+
+ $title = $incident->human_status . ': ' . $incident->name;
+ $message = '';
+ if ($this->getInput('additional_info')) {
+ if (isset($incident->occurred_at)) {
+ $message .= 'Occurred at: ' . $incident->occurred_at . "\r\n";
+ }
+ if (isset($incident->scheduled_at)) {
+ $message .= 'Scheduled at: ' . $incident->scheduled_at . "\r\n";
+ }
+ if (isset($incident->created_at)) {
+ $message .= 'Created at: ' . $incident->created_at . "\r\n";
+ }
+ if (isset($incident->updated_at)) {
+ $message .= 'Updated at: ' . $incident->updated_at . "\r\n\r\n";
+ }
+ }
+
+ $message .= $incident->message;
+ $content = nl2br($message);
+ $componentName = $this->getComponentName($incident->component_id);
+ $uidOrig = $permalink . $incident->created_at;
+ $uid = hash('sha512', $uidOrig);
+ $timestamp = strtotime($incident->created_at);
+ $categories = [];
+ $categories[] = $incident->human_status;
+ if ($componentName !== '') {
+ $categories[] = $componentName;
+ }
+
+ $item = [];
+ $item['uri'] = $permalink;
+ $item['title'] = $title;
+ $item['timestamp'] = $timestamp;
+ $item['content'] = $content;
+ $item['uid'] = $uid;
+ $item['categories'] = $categories;
+
+ $this->items[] = $item;
+ }
+ }
+}
From 473a62ed44f2141e7505de3582c1d05cf46cb38e Mon Sep 17 00:00:00 2001
From: Lyra
Date: Tue, 12 Feb 2019 15:12:04 +0100
Subject: [PATCH 19/66] [RoadAndTrackBridge] Added new bridge
---
bridges/RoadAndTrackBridge.php | 106 +++++++++++++++++++++++++++++++++
1 file changed, 106 insertions(+)
create mode 100644 bridges/RoadAndTrackBridge.php
diff --git a/bridges/RoadAndTrackBridge.php b/bridges/RoadAndTrackBridge.php
new file mode 100644
index 00000000..15a2e5aa
--- /dev/null
+++ b/bridges/RoadAndTrackBridge.php
@@ -0,0 +1,106 @@
+ array(
+ 'name' => 'New Cars',
+ 'type' => 'checkbox',
+ 'exampleValue' => 'checked',
+ 'title' => 'Activate to load New Cars articles'
+ ),
+ 'motorsports' => array(
+ 'name' => 'Motorsports',
+ 'type' => 'checkbox',
+ 'exampleValue' => 'checked',
+ 'title' => 'Activate to load Motorsports articles'
+ ),
+ 'car-culture' => array(
+ 'name' => 'Car Culture',
+ 'type' => 'checkbox',
+ 'exampleValue' => 'checked',
+ 'title' => 'Activate to load Car Culture articles'
+ ),
+ 'car-shows' => array(
+ 'name' => 'Car shows',
+ 'type' => 'checkbox',
+ 'exampleValue' => 'checked',
+ 'title' => 'Activate to load Car shows articles'
+ )
+ )
+ );
+
+ const SIG_URL = 'https://cloud.mazdigital.com/feeds/production/comboapp/204/api/v3/';
+
+ public function collectData() {
+
+ //Magic
+ $signVal = '?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9jbG91ZC5tYXpkaWd';
+ $signVal .= 'pdGFsLmNvbS9mZWVkcy9wcm9kdWN0aW9uL2NvbWJvYXBwLzIwNC8qIiwiQ29uZGl0aW9uIj';
+ $signVal .= 'p7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNTUyNTU5MDUzfSwiSXBBZGRyZ';
+ $signVal .= 'XNzIjp7IkFXUzpTb3VyY2VJcCI6IjAuMC4wLjAvMCJ9fX1dfQ__&Signature=jgS~Jccjs';
+ $signVal .= 'lXMMywWesmwDpUbHvEmrADRP7iBRzT~OiP-O~zI-8TtQzqTP7GUrpB9~v69CvhO7-JVtw94';
+ $signVal .= 'VC3N6lQrwsxTTIhpS57YGeV~MbZx~P653yUV7jb3jpJE2yUawfXnEkD-XzOIn8-caMo~14i';
+ $signVal .= 'KuWV9KNDkTJaRgOMy0rrVpWqiuBjCu5s5B8Ylt2qwcpOvHjXSqG9IY5c7GUIXKsk8yXzGFi';
+ $signVal .= 'yzy8hfuGgdx0n7fgl7c4-EoDgQaz~U76g0epejPxV5Csj16rCCfAqBU5kZJnACZ1vvOvRcV';
+ $signVal .= 'Wiu8KUuUuCS04SPmJ73Y5XoY8~uXRScxZG1kAFTIAhT4nYVlg__&Key-Pair-Id=APKAIZB';
+ $signVal .= 'QNNSW4WGIFP4Q';
+
+ $newsElements = array();
+ if($this->getInput('new-cars')) {
+ $newsElements = array_merge($newsElements,
+ json_decode(getContents(self::SIG_URL . '7591/item_feed' . $signVal))
+ );
+ }
+ if($this->getInput('motorsports')) {
+ $newsElements = array_merge($newsElements,
+ json_decode(getContents(self::SIG_URL . '7590/item_feed' . $signVal))
+ );
+ }
+ if($this->getInput('car-culture')) {
+ $newsElements = array_merge($newsElements,
+ json_decode(getContents(self::SIG_URL . '7588/item_feed' . $signVal))
+ );
+ }
+ if($this->getInput('car-shows')) {
+ $newsElements = array_merge($newsElements,
+ json_decode(getContents(self::SIG_URL . '7589/item_feed' . $signVal))
+ );
+ }
+
+ usort($newsElements, function($a, $b) {
+ return $b->published - $a->published;
+ });
+
+ $limit = 19;
+ foreach($newsElements as $element) {
+
+ $item = array();
+ $item['uri'] = $element->sourceUrl;
+ $item['timestamp'] = $element->published;
+ $item['enclosures'] = array($element->cover->url);
+ $item['title'] = $element->title;
+ $item['content'] = $this->getArticleContent($element);
+ $this->items[] = $item;
+
+ if($limit > 0) {
+ $limit--;
+ } else {
+ break;
+ }
+
+ }
+
+ }
+
+ private function getArticleContent($article) {
+
+ return getContents($article->contentUrl);
+
+ }
+}
From ae40f7b388c04cd4fe2cb7d9bb27f3503e10c882 Mon Sep 17 00:00:00 2001
From: Nono
Date: Tue, 19 Feb 2019 21:50:00 +0100
Subject: [PATCH 20/66] [MozillaSecurityBridge] Make the URI unique by adding
timestamp (#1005)
* added unique UID + URI
if UID is mandatory for RSS-Bridge, the unicity of the URI is also mandatory for some reader (like kriss feed).
---
bridges/MozillaSecurityBridge.php | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/bridges/MozillaSecurityBridge.php b/bridges/MozillaSecurityBridge.php
index 0b951a14..52672f56 100644
--- a/bridges/MozillaSecurityBridge.php
+++ b/bridges/MozillaSecurityBridge.php
@@ -21,7 +21,8 @@ class MozillaSecurityBridge extends BridgeAbstract {
$item['title'] = $element->innertext;
$item['timestamp'] = strtotime($element->innertext);
$item['content'] = $element->next_sibling()->innertext;
- $item['uri'] = self::URI;
+ $item['uri'] = self::URI . '?' . $item['timestamp'];
+ $item['uid'] = self::URI . '?' . $item['timestamp'];
$this->items[] = $item;
}
}
From 777c20483804059e8d7734f40f74b9ccb4670ba7 Mon Sep 17 00:00:00 2001
From: Nono
Date: Tue, 19 Feb 2019 21:53:20 +0100
Subject: [PATCH 21/66] [VMwareSecurityBridge] New Bridge (#1041)
* Create VMwareSecurityBridge.php
---
bridges/VMwareSecurityBridge.php | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
create mode 100644 bridges/VMwareSecurityBridge.php
diff --git a/bridges/VMwareSecurityBridge.php b/bridges/VMwareSecurityBridge.php
new file mode 100644
index 00000000..326d26a8
--- /dev/null
+++ b/bridges/VMwareSecurityBridge.php
@@ -0,0 +1,31 @@
+find('div[class="news_block"]');
+
+ foreach ($articles as $element) {
+ $item['uri'] = $element->find('a', 0)->getAttribute('href');
+ $title = $element->find('a', 0)->innertext;
+ $item['title'] = $title;
+ $item['timestamp'] = strtotime($element->find('p', 0)->innertext);
+ $item['content'] = $element->find('p', 1)->innertext;
+ $item['uid'] = $title;
+
+ $this->items[] = $item;
+ }
+ }
+}
From f9ed934c8cc0ee4fcede8c69f88bf83b4d742558 Mon Sep 17 00:00:00 2001
From: Lyra
Date: Tue, 19 Feb 2019 22:05:06 +0100
Subject: [PATCH 22/66] Update contributors and bump version
---
README.md | 3 +++
lib/Configuration.php | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4e2c7b1c..3dbcad78 100644
--- a/README.md
+++ b/README.md
@@ -165,12 +165,14 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [hunhejj](https://github.com/hunhejj)
* [j0k3r](https://github.com/j0k3r)
* [jdigilio](https://github.com/jdigilio)
+ * [klimplant](https://github.com/klimplant)
* [kranack](https://github.com/kranack)
* [kraoc](https://github.com/kraoc)
* [laBecasse](https://github.com/laBecasse)
* [lagaisse](https://github.com/lagaisse)
* [lalannev](https://github.com/lalannev)
* [ldidry](https://github.com/ldidry)
+ * [Limero](https://github.com/Limero)
* [lorenzos](https://github.com/lorenzos)
* [m0zes](https://github.com/m0zes)
* [matthewseal](https://github.com/matthewseal)
@@ -185,6 +187,7 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [mxmehl](https://github.com/mxmehl)
* [nel50n](https://github.com/nel50n)
* [niawag](https://github.com/niawag)
+ * [Nova](https://github.com/l1n)
* [pellaeon](https://github.com/pellaeon)
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
* [pitchoule](https://github.com/pitchoule)
diff --git a/lib/Configuration.php b/lib/Configuration.php
index 3a2a7bf3..0fdcbda5 100644
--- a/lib/Configuration.php
+++ b/lib/Configuration.php
@@ -28,7 +28,7 @@ final class Configuration {
*
* @todo Replace this property by a constant.
*/
- public static $VERSION = 'dev.2019-01-13';
+ public static $VERSION = 'dev.2019-02-19';
/**
* Holds the configuration data.
From 3d24596a52ef4540631c66bb0cdac728395e01fa Mon Sep 17 00:00:00 2001
From: somini
Date: Sun, 24 Feb 2019 10:47:29 +0000
Subject: [PATCH 23/66] [AsahiShimbunAJW] Add new Bridge (#1036)
---
bridges/AsahiShimbunAJWBridge.php | 72 +++++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
create mode 100644 bridges/AsahiShimbunAJWBridge.php
diff --git a/bridges/AsahiShimbunAJWBridge.php b/bridges/AsahiShimbunAJWBridge.php
new file mode 100644
index 00000000..0ceb0381
--- /dev/null
+++ b/bridges/AsahiShimbunAJWBridge.php
@@ -0,0 +1,72 @@
+ array(
+ 'type' => 'list',
+ 'name' => 'Section',
+ 'values' => array(
+ 'Japan » Social Affairs' => 'japan/social',
+ 'Japan » People' => 'japan/people',
+ 'Japan » 3/11 Disaster' => 'japan/0311disaster',
+ 'Japan » Sci & Tech' => 'japan/sci_tech',
+ 'Politics' => 'politics',
+ 'Business' => 'business',
+ 'Culture » Style' => 'culture/style',
+ 'Culture » Movies' => 'culture/movies',
+ 'Culture » Manga & Anime' => 'culture/manga_anime',
+ 'Asia » China' => 'asia/china',
+ 'Asia » Korean Peninsula' => 'asia/korean_peninsula',
+ 'Asia » Around Asia' => 'asia/around_asia',
+ 'Opinion » Editorial' => 'opinion/editorial',
+ 'Opinion » Vox Populi' => 'opinion/vox',
+ ),
+ 'defaultValue' => 'Politics',
+ )
+ )
+ );
+
+ private function getSectionURI($section) {
+ return self::getURI() . $section . '/';
+ }
+
+ public function collectData() {
+ $html = getSimpleHTMLDOM($this->getSectionURI($this->getInput('section')))
+ or returnServerError('Could not load content');
+
+ foreach($html->find('#MainInner li a') as $element) {
+ if ($element->parent()->class == 'HeadlineTopImage-S') {
+ Debug::log('Skip Headline, it is repeated below');
+ continue;
+ }
+ $item = array();
+
+ $item['uri'] = self::BASE_URI . $element->href;
+ $e_lead = $element->find('span.Lead', 0);
+ if ($e_lead) {
+ $item['content'] = $e_lead->innertext;
+ $e_lead->outertext = '';
+ } else {
+ $item['content'] = $element->innertext;
+ }
+ $e_date = $element->find('span.EnDate', 0);
+ if ($e_date) {
+ $item['timestamp'] = strtotime($e_date->innertext);
+ $e_date->outertext = '';
+ }
+ $e_video = $element->find('span.EnVideo', 0);
+ if ($e_video) {
+ $e_video->outertext = '';
+ $element->innertext = "VIDEO: $element->innertext";
+ }
+ $item['title'] = $element->innertext;
+
+ $this->items[] = $item;
+ }
+ }
+}
From 958ba815c72c3ade0401702601273cacc07e8127 Mon Sep 17 00:00:00 2001
From: fulmeek <36341513+fulmeek@users.noreply.github.com>
Date: Sun, 24 Feb 2019 11:49:17 +0100
Subject: [PATCH 24/66] [OneFortuneADayBridge] Add lucky number feature (#1038)
---
bridges/OneFortuneADayBridge.php | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/bridges/OneFortuneADayBridge.php b/bridges/OneFortuneADayBridge.php
index ed0b5ec6..31296041 100644
--- a/bridges/OneFortuneADayBridge.php
+++ b/bridges/OneFortuneADayBridge.php
@@ -35,25 +35,33 @@ class OneFortuneADayBridge extends BridgeAbstract {
'23:00' => 23,
),
'defaultValue' => 5
+ ),
+ 'lucky' => array(
+ 'name' => 'Lucky number (optional)',
+ 'type' => 'text'
)
));
const LIMIT_ITEMS = 7;
const DAY_SECS = 86400;
+ public function getDescription(){
+ return self::DESCRIPTION . '
Set a lucky number to get your personal quotes, like ' . mt_rand();
+ }
+
public function collectData() {
$time = gmmktime((int)$this->getInput('time'), 0, 0);
if ($time > time())
$time -= self::DAY_SECS;
for ($i = self::LIMIT_ITEMS; $i > 0; --$i) {
- $seed = date('Ymd', $time);
+ $seed = date('Ymd', $time) . $this->getInput('lucky');
$quote = $this->getQuote($seed);
$item['title'] = strftime('%A, %x', $time);
$item['content'] = htmlentities($quote, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = $time;
- $item['uri'] = 'urn:sha1:' . hash('sha1', $seed);
+ $item['uid'] = hash('sha1', $seed);
$this->items[] = $item;
From e3588f62bde583a335b0cfc024b7b7baba35db5c Mon Sep 17 00:00:00 2001
From: logmanoriginal
Date: Sun, 24 Feb 2019 11:56:43 +0100
Subject: [PATCH 25/66] [Cache] Fix cache types ending on 'cache' are not
detected correctly
References #1000
---
lib/Cache.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/Cache.php b/lib/Cache.php
index 6826d4cc..6c2943ad 100644
--- a/lib/Cache.php
+++ b/lib/Cache.php
@@ -193,7 +193,7 @@ class Cache {
}
// Trim trailing 'Cache' if exists
- if(preg_match('/(.+)(?:Cache)/i', $name, $matches)) {
+ if(preg_match('/(.+)(?:Cache)$/i', $name, $matches)) {
$name = $matches[1];
}
From dc1b1b13cc39aae6d1e69b0633e0420ed91a5da4 Mon Sep 17 00:00:00 2001
From: fulmeek <36341513+fulmeek@users.noreply.github.com>
Date: Sun, 24 Feb 2019 12:04:27 +0100
Subject: [PATCH 26/66] [SQLiteCache] Implement cache based on SQLite 3 (#1035)
---
caches/SQLiteCache.php | 96 +++++++++++++++++++++++++++++++
tests/CacheImplementationTest.php | 4 +-
2 files changed, 97 insertions(+), 3 deletions(-)
create mode 100644 caches/SQLiteCache.php
diff --git a/caches/SQLiteCache.php b/caches/SQLiteCache.php
new file mode 100644
index 00000000..b3caac34
--- /dev/null
+++ b/caches/SQLiteCache.php
@@ -0,0 +1,96 @@
+
+ */
+class SQLiteCache implements CacheInterface {
+ protected $path;
+ protected $param;
+
+ private $db = null;
+
+ public function __construct() {
+ $file = PATH_CACHE . 'cache.sqlite';
+
+ if (!is_file($file)) {
+ $this->db = new SQLite3($file);
+ $this->db->enableExceptions(true);
+ $this->db->exec("CREATE TABLE storage ('key' BLOB PRIMARY KEY, 'value' BLOB, 'updated' INTEGER)");
+ } else {
+ $this->db = new SQLite3($file);
+ $this->db->enableExceptions(true);
+ }
+ $this->db->busyTimeout(5000);
+ }
+
+ public function loadData(){
+ $Qselect = $this->db->prepare('SELECT value FROM storage WHERE key = :key');
+ $Qselect->bindValue(':key', $this->getCacheKey());
+ $result = $Qselect->execute();
+ if ($result instanceof SQLite3Result) {
+ $data = $result->fetchArray(SQLITE3_ASSOC);
+ if (isset($data['value'])) {
+ return unserialize($data['value']);
+ }
+ }
+
+ return null;
+ }
+
+ public function saveData($datas){
+ $Qupdate = $this->db->prepare('INSERT OR REPLACE INTO storage (key, value, updated) VALUES (:key, :value, :updated)');
+ $Qupdate->bindValue(':key', $this->getCacheKey());
+ $Qupdate->bindValue(':value', serialize($datas));
+ $Qupdate->bindValue(':updated', time());
+ $Qupdate->execute();
+
+ return $this;
+ }
+
+ public function getTime(){
+ $Qselect = $this->db->prepare('SELECT updated FROM storage WHERE key = :key');
+ $Qselect->bindValue(':key', $this->getCacheKey());
+ $result = $Qselect->execute();
+ if ($result instanceof SQLite3Result) {
+ $data = $result->fetchArray(SQLITE3_ASSOC);
+ if (isset($data['updated'])) {
+ return $data['updated'];
+ }
+ }
+
+ return false;
+ }
+
+ public function purgeCache($duration){
+ $Qdelete = $this->db->prepare('DELETE FROM storage WHERE updated < :expired');
+ $Qdelete->bindValue(':expired', time() - $duration);
+ $Qdelete->execute();
+ }
+
+ /**
+ * Set cache path
+ * @return self
+ */
+ public function setPath($path){
+ $this->path = $path;
+ return $this;
+ }
+
+ /**
+ * Set HTTP GET parameters
+ * @return self
+ */
+ public function setParameters(array $param){
+ $this->param = array_map('strtolower', $param);
+ return $this;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected function getCacheKey(){
+ if(is_null($this->param)) {
+ throw new \Exception('Call "setParameters" first!');
+ }
+
+ return hash('sha1', $this->path . http_build_query($this->param), true);
+ }
+}
diff --git a/tests/CacheImplementationTest.php b/tests/CacheImplementationTest.php
index 7eb1af51..25189134 100644
--- a/tests/CacheImplementationTest.php
+++ b/tests/CacheImplementationTest.php
@@ -5,7 +5,6 @@ use PHPUnit\Framework\TestCase;
class CacheImplementationTest extends TestCase {
private $class;
- private $obj;
/**
* @dataProvider dataCachesProvider
@@ -22,7 +21,7 @@ class CacheImplementationTest extends TestCase {
*/
public function testClassType($path) {
$this->setCache($path);
- $this->assertInstanceOf(CacheInterface::class, $this->obj);
+ $this->assertTrue(is_subclass_of($this->class, CacheInterface::class), 'class must be subclass of CacheInterface');
}
////////////////////////////////////////////////////////////////////////////
@@ -39,6 +38,5 @@ class CacheImplementationTest extends TestCase {
require_once $path;
$this->class = basename($path, '.php');
$this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist');
- $this->obj = new $this->class();
}
}
From b96c25a3afac65bf5b1760c4f5495f848a7fd1a7 Mon Sep 17 00:00:00 2001
From: Ryan Liptak
Date: Sun, 24 Feb 2019 03:08:34 -0800
Subject: [PATCH 27/66] [BandcampBridge] Update to use newer POST API (#1045)
Bandcamp tags pages have a new layout and now use a POST API endpoint to view each page of releases.
Output of this bridge should be almost the same as before, with a few small improvements:
- Small album image in 'content', larger album image in 'enclosures'
- RSS item titles/authors are appended with the releaser in parentheses if the artist name and the releaser are different (i.e. Record Label's Bandcamp releases an album called Bar by the band named Foo, it would get the title 'Foo - Bar (Record Label)' and the author 'Foo (Record Label)')
---
bridges/BandcampBridge.php | 76 +++++++++++++++++++++++++-------------
1 file changed, 50 insertions(+), 26 deletions(-)
diff --git a/bridges/BandcampBridge.php b/bridges/BandcampBridge.php
index 9c8d436e..6c75ed5e 100644
--- a/bridges/BandcampBridge.php
+++ b/bridges/BandcampBridge.php
@@ -13,48 +13,72 @@ class BandcampBridge extends BridgeAbstract {
'required' => true
)
));
+ const IMGURI = 'https://f4.bcbits.com/';
+ const IMGSIZE_300PX = 23;
+ const IMGSIZE_700PX = 16;
public function getIcon() {
return 'https://s4.bcbits.com/img/bc_favicon.ico';
}
public function collectData(){
- $html = getSimpleHTMLDOM($this->getURI())
- or returnServerError('No results for this query.');
+ $url = self::URI . 'api/hub/1/dig_deeper';
+ $data = $this->buildRequestJson();
+ $header = array(
+ 'Content-Type: application/json',
+ 'Content-Length: ' . strlen($data)
+ );
+ $opts = array(
+ CURLOPT_CUSTOMREQUEST => 'POST',
+ CURLOPT_POSTFIELDS => $data
+ );
+ $content = getContents($url, $header, $opts)
+ or returnServerError('Could not complete request to: ' . $url);
- foreach($html->find('li.item') as $release) {
- $script = $release->find('div.art', 0)->getAttribute('onclick');
- $uri = ltrim($script, "return 'url(");
- $uri = rtrim($uri, "')");
+ $json = json_decode($content);
- $item = array();
- $item['author'] = $release->find('div.itemsubtext', 0)->plaintext
- . ' - '
- . $release->find('div.itemtext', 0)->plaintext;
+ if ($json->ok !== true) {
+ returnServerError('Invalid response');
+ }
- $item['title'] = $release->find('div.itemsubtext', 0)->plaintext
- . ' - '
- . $release->find('div.itemtext', 0)->plaintext;
+ foreach ($json->items as $entry) {
+ $url = $entry->tralbum_url;
+ $artist = $entry->artist;
+ $title = $entry->title;
+ // e.g. record label is the releaser, but not the artist
+ $releaser = $entry->band_name !== $entry->artist ? $entry->band_name : null;
- $item['content'] = '
'
- . $release->find('div.itemsubtext', 0)->plaintext
- . ' - '
- . $release->find('div.itemtext', 0)->plaintext;
+ $full_title = $artist . ' - ' . $title;
+ $full_artist = $artist;
+ if (isset($releaser)) {
+ $full_title .= ' (' . $releaser . ')';
+ $full_artist .= ' (' . $releaser . ')';
+ }
+ $small_img = $this->getImageUrl($entry->art_id, self::IMGSIZE_300PX);
+ $img = $this->getImageUrl($entry->art_id, self::IMGSIZE_700PX);
- $item['id'] = $release->find('a', 0)->getAttribute('href');
- $item['uri'] = $release->find('a', 0)->getAttribute('href');
+ $item = array(
+ 'uri' => $url,
+ 'author' => $full_artist,
+ 'title' => $full_title
+ );
+ $item['content'] = "
$full_title";
+ $item['enclosures'] = array($img);
$this->items[] = $item;
}
}
- public function getURI(){
- if(!is_null($this->getInput('tag'))) {
- return self::URI . 'tag/' . urlencode($this->getInput('tag')) . '?sort_field=date';
- }
+ private function buildRequestJson(){
+ $requestJson = array(
+ 'tag' => $this->getInput('tag'),
+ 'page' => 1,
+ 'sort' => 'date'
+ );
+ return json_encode($requestJson);
+ }
- return parent::getURI();
+ private function getImageUrl($id, $size){
+ return self::IMGURI . 'img/a' . $id . '_' . $size . '.jpg';
}
public function getName(){
From d37f0c14a094d747987d6458766a7bf3238849f4 Mon Sep 17 00:00:00 2001
From: ORelio
Date: Sat, 2 Mar 2019 19:03:29 +0100
Subject: [PATCH 28/66] [LeMondeInformatique] Handle special articles (#1039)
Fix content extraction for special article compiling previous articles
---
bridges/LeMondeInformatiqueBridge.php | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/bridges/LeMondeInformatiqueBridge.php b/bridges/LeMondeInformatiqueBridge.php
index 09bcf6a3..45aa6075 100644
--- a/bridges/LeMondeInformatiqueBridge.php
+++ b/bridges/LeMondeInformatiqueBridge.php
@@ -20,12 +20,13 @@ class LeMondeInformatiqueBridge extends FeedExpander {
str_replace(
'/grande/',
'/petite/',
- $article_html->find('.article-image', 0)->find('img', 0)->src
+ $article_html->find('.article-image > img, figure > img', 0)->src
)
);
//No response header sets the encoding, explicit conversion is needed or subsequent xml_encode() will fail
- $item['content'] = utf8_encode($this->cleanArticle($article_html->find('div.col-primary', 0)->innertext));
+ $content_node = $article_html->find('div.col-primary, div.col-sm-9', 0);
+ $item['content'] = utf8_encode($this->cleanArticle($content_node->innertext));
$item['author'] = utf8_encode($article_html->find('div.author-infos', 0)->find('b', 0)->plaintext);
return $item;
From dac685b88719a280ab36a9210a0c3ee57e4fa3b0 Mon Sep 17 00:00:00 2001
From: somini
Date: Sat, 2 Mar 2019 18:05:23 +0000
Subject: [PATCH 29/66] [ComboiosDePortugalBridge] Add new bridge (#1049)
---
bridges/ComboiosDePortugalBridge.php | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 bridges/ComboiosDePortugalBridge.php
diff --git a/bridges/ComboiosDePortugalBridge.php b/bridges/ComboiosDePortugalBridge.php
new file mode 100644
index 00000000..4d82881f
--- /dev/null
+++ b/bridges/ComboiosDePortugalBridge.php
@@ -0,0 +1,22 @@
+getURI() . '/consultar-horarios/avisos')
+ or returnServerError('Could not load content');
+
+ foreach($html->find('.warnings-table a') as $element) {
+ $item = array();
+
+ $item['title'] = $element->innertext;
+ $item['uri'] = self::BASE_URI . $element->href;
+
+ $this->items[] = $item;
+ }
+ }
+}
From 9d85b951f78fe9a7ab292ea7072b6c5baf520ad4 Mon Sep 17 00:00:00 2001
From: fulmeek <36341513+fulmeek@users.noreply.github.com>
Date: Sat, 2 Mar 2019 19:09:16 +0100
Subject: [PATCH 30/66] [BakaUpdatesMangaReleasesBridge] rework to parse new
layout (#1052)
* rework to parse new layout
* skip incomplete rows
The last row could have fewer columns if there are less rows than the items limit. This usually should not happen, though.
* use constant for skipping
---
bridges/BakaUpdatesMangaReleasesBridge.php | 36 ++++++++++------------
1 file changed, 17 insertions(+), 19 deletions(-)
diff --git a/bridges/BakaUpdatesMangaReleasesBridge.php b/bridges/BakaUpdatesMangaReleasesBridge.php
index e6da64b6..eeeeb4d7 100644
--- a/bridges/BakaUpdatesMangaReleasesBridge.php
+++ b/bridges/BakaUpdatesMangaReleasesBridge.php
@@ -12,6 +12,7 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
'exampleValue' => '12345'
)
));
+ const LIMIT_COLS = 5;
const LIMIT_ITEMS = 10;
private $feedName = '';
@@ -20,21 +21,21 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Series not found');
- $objTitle = $html->find('td[class="text pad"]', 1);
- if ($objTitle)
- $this->feedName = $objTitle->plaintext;
-
- $itemlist = $html->find('td#main_content table table table tr');
- if (!$itemlist)
+ // content is an unstructured pile of divs, ugly to parse
+ $cols = $html->find('div#main_content div.row > div.text');
+ if (!$cols)
returnServerError('No releases');
- $limit = self::LIMIT_ITEMS;
- foreach($itemlist as $element) {
- $cols = $element->find('td[class="text pad"]');
- if (!$cols)
- continue;
- if ($limit <= 0)
- break;
+ $rows = array_slice(
+ array_chunk($cols, self::LIMIT_COLS), 0, self::LIMIT_ITEMS
+ );
+
+ if (isset($rows[0][1])) {
+ $this->feedName = html_entity_decode($rows[0][1]->plaintext);
+ }
+
+ foreach($rows as $cols) {
+ if (count($cols) < self::LIMIT_COLS) continue;
$item = array();
$title = array();
@@ -65,14 +66,11 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
$item['content'] .= 'Groups: ' . $objAuthor->innertext . '
';
}
- $item['title'] = implode(' ', $title);
- $item['uri'] = $this->getURI() . '#' . hash('sha1', $item['title']);
+ $item['title'] = implode(' ', $title);
+ $item['uri'] = $this->getURI();
+ $item['uid'] = hash('sha1', $item['title']);
$this->items[] = $item;
-
- if(count($this->items) >= $limit) {
- break;
- }
}
}
From 688c950916d79deb6f400816b9e8f011cf0c5381 Mon Sep 17 00:00:00 2001
From: sysadminstory
Date: Sat, 2 Mar 2019 19:10:57 +0100
Subject: [PATCH 31/66] [DealabsBridge] Patch unparsable Deal date (#1053)
In case of a unparsable date, the text to DateTime object failed, and
this resulted to a Fatal error while using this DateTime object . To
prvent this fatal error, if the date parsing failse, then a DateTime
object is created with the actual date.
---
bridges/DealabsBridge.php | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/bridges/DealabsBridge.php b/bridges/DealabsBridge.php
index 3427ca65..b64bb1dc 100644
--- a/bridges/DealabsBridge.php
+++ b/bridges/DealabsBridge.php
@@ -1376,8 +1376,11 @@ class PepperBridgeAbstract extends BridgeAbstract {
// Add the Hour and minutes
$date_str .= ' 00:00';
-
$date = DateTime::createFromFormat('j F Y H:i', $date_str);
+ // In some case, the date is not recognized : as a workaround the actual date is taken
+ if($date === false) {
+ $date = new DateTime();
+ }
return $date->getTimestamp();
}
From f450b2e1186dd14a9b47e703c39ade608306945c Mon Sep 17 00:00:00 2001
From: logmanoriginal
Date: Sat, 2 Mar 2019 19:33:33 +0100
Subject: [PATCH 32/66] [SQLiteCache] Check sqlite3 extension in __construct
Checks if the sqlite3 extension is loaded and throws an error
if it's missing.
---
caches/SQLiteCache.php | 3 +++
1 file changed, 3 insertions(+)
diff --git a/caches/SQLiteCache.php b/caches/SQLiteCache.php
index b3caac34..5cbb3772 100644
--- a/caches/SQLiteCache.php
+++ b/caches/SQLiteCache.php
@@ -9,6 +9,9 @@ class SQLiteCache implements CacheInterface {
private $db = null;
public function __construct() {
+ if (!extension_loaded('sqlite3'))
+ die('"sqlite3" extension not loaded. Please check "php.ini"');
+
$file = PATH_CACHE . 'cache.sqlite';
if (!is_file($file)) {
From 1ac66b3fdcc27ed8f690dc790243d45fb26e44f0 Mon Sep 17 00:00:00 2001
From: LogMANOriginal
Date: Sat, 2 Mar 2019 19:42:40 +0100
Subject: [PATCH 33/66] [README] Add sqlite3 as requirement for SQLiteCache
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 3dbcad78..39a9c67b 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,7 @@ RSS-Bridge requires PHP 5.6 or higher with following extensions enabled:
- [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php)
- [`curl`](https://secure.php.net/manual/en/book.curl.php)
- [`json`](https://secure.php.net/manual/en/book.json.php)
+ - [`sqlite3`](http://php.net/manual/en/book.sqlite3.php) (only when using SQLiteCache)
Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
From 326a70773911eaf5229bdb1f5bb4cf18fac64c08 Mon Sep 17 00:00:00 2001
From: Roliga
Date: Tue, 12 Mar 2019 13:29:11 +0100
Subject: [PATCH 34/66] [SoundcloudBridge] Update API key (#1062)
---
bridges/SoundcloudBridge.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bridges/SoundcloudBridge.php b/bridges/SoundcloudBridge.php
index 91ac2b5c..30958566 100644
--- a/bridges/SoundcloudBridge.php
+++ b/bridges/SoundcloudBridge.php
@@ -14,7 +14,7 @@ class SoundCloudBridge extends BridgeAbstract {
)
));
- const CLIENT_ID = '4jkoEFmZEDaqjwJ9Eih4ATNhcH3vMVfp';
+ const CLIENT_ID = 'W0KEWWILAjDiRH89X0jpwzuq6rbSK08R';
public function collectData(){
From 696afa96d3a1eac80b030656a1aeb36fc20a22db Mon Sep 17 00:00:00 2001
From: fulmeek <36341513+fulmeek@users.noreply.github.com>
Date: Thu, 14 Mar 2019 19:43:00 +0100
Subject: [PATCH 35/66] [BakaUpdatesMangaReleasesBridge] filter title and
groups (#1058)
Baka-Updates Manga uses an asterisk (*) to denote series information have
been updated within the last 24 hours. This is not helpful in a feed.
---
bridges/BakaUpdatesMangaReleasesBridge.php | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/bridges/BakaUpdatesMangaReleasesBridge.php b/bridges/BakaUpdatesMangaReleasesBridge.php
index eeeeb4d7..cde9be84 100644
--- a/bridges/BakaUpdatesMangaReleasesBridge.php
+++ b/bridges/BakaUpdatesMangaReleasesBridge.php
@@ -31,7 +31,7 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
);
if (isset($rows[0][1])) {
- $this->feedName = html_entity_decode($rows[0][1]->plaintext);
+ $this->feedName = $this->filterHTML($rows[0][1]->plaintext);
}
foreach($rows as $cols) {
@@ -48,8 +48,8 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
$objTitle = $cols[1];
if ($objTitle) {
- $title[] = html_entity_decode($objTitle->plaintext);
- $item['content'] .= 'Series: ' . $objTitle->innertext . '
';
+ $title[] = $this->filterHTML($objTitle->plaintext);
+ $item['content'] .= 'Series: ' . $this->filterText($objTitle->innertext) . '
';
}
$objVolume = $cols[2];
@@ -62,8 +62,8 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
$objAuthor = $cols[4];
if ($objAuthor && !empty($objAuthor->plaintext)) {
- $item['author'] = html_entity_decode($objAuthor->plaintext);
- $item['content'] .= 'Groups: ' . $objAuthor->innertext . '
';
+ $item['author'] = $this->filterHTML($objAuthor->plaintext);
+ $item['content'] .= 'Groups: ' . $this->filterText($objAuthor->innertext) . '
';
}
$item['title'] = implode(' ', $title);
@@ -88,4 +88,12 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
}
return parent::getName();
}
+
+ private function filterText($text) {
+ return rtrim($text, '*');
+ }
+
+ private function filterHTML($text) {
+ return $this->filterText(html_entity_decode($text));
+ }
}
From 5ea985164ed8f52a53f12f76e529267a2904102a Mon Sep 17 00:00:00 2001
From: fulmeek <36341513+fulmeek@users.noreply.github.com>
Date: Thu, 14 Mar 2019 19:44:36 +0100
Subject: [PATCH 36/66] [OneFortuneADayBridge] use date in UTC for seed (#1059)
---
bridges/OneFortuneADayBridge.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bridges/OneFortuneADayBridge.php b/bridges/OneFortuneADayBridge.php
index 31296041..62fe767d 100644
--- a/bridges/OneFortuneADayBridge.php
+++ b/bridges/OneFortuneADayBridge.php
@@ -55,7 +55,7 @@ class OneFortuneADayBridge extends BridgeAbstract {
$time -= self::DAY_SECS;
for ($i = self::LIMIT_ITEMS; $i > 0; --$i) {
- $seed = date('Ymd', $time) . $this->getInput('lucky');
+ $seed = gmdate('Ymd', $time) . $this->getInput('lucky');
$quote = $this->getQuote($seed);
$item['title'] = strftime('%A, %x', $time);
From 5b80bcaa0422424e07134eb4dc42277833f9e2ce Mon Sep 17 00:00:00 2001
From: logmanoriginal
Date: Sun, 17 Mar 2019 20:28:15 +0100
Subject: [PATCH 37/66] [README] Update list of contributors
The list acutally didn't change, but it's sorted properly now
(thanks to em92 for the suggestion)
---
README.md | 62 +++++++++++++++++++++++++++----------------------------
1 file changed, 31 insertions(+), 31 deletions(-)
diff --git a/README.md b/README.md
index 39a9c67b..7ed74d81 100644
--- a/README.md
+++ b/README.md
@@ -112,41 +112,16 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
-->
* [16mhz](https://github.com/16mhz)
+ * [adamchainz](https://github.com/adamchainz)
* [Ahiles3005](https://github.com/Ahiles3005)
* [Albirew](https://github.com/Albirew)
+ * [aledeg](https://github.com/aledeg)
+ * [alexAubin](https://github.com/alexAubin)
* [AmauryCarrade](https://github.com/AmauryCarrade)
* [AntoineTurmel](https://github.com/AntoineTurmel)
* [ArthurHoaro](https://github.com/ArthurHoaro)
* [Astalaseven](https://github.com/Astalaseven)
* [Astyan-42](https://github.com/Astyan-42)
- * [Daiyousei](https://github.com/Daiyousei)
- * [Djuuu](https://github.com/Djuuu)
- * [Draeli](https://github.com/Draeli)
- * [EtienneM](https://github.com/EtienneM)
- * [Frenzie](https://github.com/Frenzie)
- * [Ginko-Aloe](https://github.com/Ginko-Aloe)
- * [Glandos](https://github.com/Glandos)
- * [GregThib](https://github.com/GregThib)
- * [Grummfy](https://github.com/Grummfy)
- * [JackNUMBER](https://github.com/JackNUMBER)
- * [JeremyRand](https://github.com/JeremyRand)
- * [Jocker666z](https://github.com/Jocker666z)
- * [LogMANOriginal](https://github.com/LogMANOriginal)
- * [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
- * [Nono-m0le](https://github.com/Nono-m0le)
- * [ORelio](https://github.com/ORelio)
- * [PaulVayssiere](https://github.com/PaulVayssiere)
- * [Piranhaplant](https://github.com/Piranhaplant)
- * [Riduidel](https://github.com/Riduidel)
- * [Roliga](https://github.com/Roliga)
- * [Strubbl](https://github.com/Strubbl)
- * [TheRadialActive](https://github.com/TheRadialActive)
- * [TwizzyDizzy](https://github.com/TwizzyDizzy)
- * [WalterBarrett](https://github.com/WalterBarrett)
- * [ZeNairolf](https://github.com/ZeNairolf)
- * [adamchainz](https://github.com/adamchainz)
- * [aledeg](https://github.com/aledeg)
- * [alexAubin](https://github.com/alexAubin)
* [az5he6ch](https://github.com/az5he6ch)
* [b1nj](https://github.com/b1nj)
* [benasse](https://github.com/benasse)
@@ -157,23 +132,37 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [corenting](https://github.com/corenting)
* [couraudt](https://github.com/couraudt)
* [da2x](https://github.com/da2x)
+ * [Daiyousei](https://github.com/Daiyousei)
* [disk0x](https://github.com/disk0x)
- * [eMerzh](https://github.com/eMerzh)
+ * [Djuuu](https://github.com/Djuuu)
+ * [Draeli](https://github.com/Draeli)
* [em92](https://github.com/em92)
+ * [eMerzh](https://github.com/eMerzh)
+ * [EtienneM](https://github.com/EtienneM)
* [fluffy-critter](https://github.com/fluffy-critter)
+ * [Frenzie](https://github.com/Frenzie)
* [fulmeek](https://github.com/fulmeek)
+ * [Ginko-Aloe](https://github.com/Ginko-Aloe)
+ * [Glandos](https://github.com/Glandos)
+ * [GregThib](https://github.com/GregThib)
* [griffaurel](https://github.com/griffaurel)
+ * [Grummfy](https://github.com/Grummfy)
* [hunhejj](https://github.com/hunhejj)
* [j0k3r](https://github.com/j0k3r)
+ * [JackNUMBER](https://github.com/JackNUMBER)
* [jdigilio](https://github.com/jdigilio)
+ * [JeremyRand](https://github.com/JeremyRand)
+ * [Jocker666z](https://github.com/Jocker666z)
* [klimplant](https://github.com/klimplant)
* [kranack](https://github.com/kranack)
* [kraoc](https://github.com/kraoc)
+ * [l1n](https://github.com/l1n)
* [laBecasse](https://github.com/laBecasse)
* [lagaisse](https://github.com/lagaisse)
* [lalannev](https://github.com/lalannev)
* [ldidry](https://github.com/ldidry)
* [Limero](https://github.com/Limero)
+ * [LogMANOriginal](https://github.com/LogMANOriginal)
* [lorenzos](https://github.com/lorenzos)
* [m0zes](https://github.com/m0zes)
* [matthewseal](https://github.com/matthewseal)
@@ -183,29 +172,40 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [metaMMA](https://github.com/metaMMA)
* [mickael-bertrand](https://github.com/mickael-bertrand)
* [mitsukarenai](https://github.com/mitsukarenai)
+ * [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
* [mr-flibble](https://github.com/mr-flibble)
* [mro](https://github.com/mro)
* [mxmehl](https://github.com/mxmehl)
* [nel50n](https://github.com/nel50n)
* [niawag](https://github.com/niawag)
- * [Nova](https://github.com/l1n)
+ * [Nono-m0le](https://github.com/Nono-m0le)
+ * [ORelio](https://github.com/ORelio)
+ * [PaulVayssiere](https://github.com/PaulVayssiere)
* [pellaeon](https://github.com/pellaeon)
+ * [Piranhaplant](https://github.com/Piranhaplant)
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
* [pitchoule](https://github.com/pitchoule)
* [pmaziere](https://github.com/pmaziere)
* [prysme01](https://github.com/prysme01)
* [quentinus95](https://github.com/quentinus95)
- * [qwertygc](https://github.com/qwertygc)
* [regisenguehard](https://github.com/regisenguehard)
+ * [Riduidel](https://github.com/Riduidel)
* [rogerdc](https://github.com/rogerdc)
+ * [Roliga](https://github.com/Roliga)
* [sebsauvage](https://github.com/sebsauvage)
+ * [somini](https://github.com/somini)
+ * [squeek502](https://github.com/squeek502)
+ * [Strubbl](https://github.com/Strubbl)
* [sublimz](https://github.com/sublimz)
* [sysadminstory](https://github.com/sysadminstory)
* [tameroski](https://github.com/tameroski)
* [teromene](https://github.com/teromene)
+ * [TheRadialActive](https://github.com/TheRadialActive)
* [triatic](https://github.com/triatic)
+ * [WalterBarrett](https://github.com/WalterBarrett)
* [wtuuju](https://github.com/wtuuju)
* [yardenac](https://github.com/yardenac)
+ * [ZeNairolf](https://github.com/ZeNairolf)
Licenses
===
From ae2c35c18a28bd68d388652215501f475032faa9 Mon Sep 17 00:00:00 2001
From: logmanoriginal
Date: Sun, 17 Mar 2019 20:28:55 +0100
Subject: [PATCH 38/66] [Configuration] Bump version to 2019-03-17
---
lib/Configuration.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/Configuration.php b/lib/Configuration.php
index 0fdcbda5..7d3a0f50 100644
--- a/lib/Configuration.php
+++ b/lib/Configuration.php
@@ -28,7 +28,7 @@ final class Configuration {
*
* @todo Replace this property by a constant.
*/
- public static $VERSION = 'dev.2019-02-19';
+ public static $VERSION = '2019-03-17';
/**
* Holds the configuration data.
From d7094b7feb316d666877d01d91d9c042a31be006 Mon Sep 17 00:00:00 2001
From: logmanoriginal
Date: Sun, 17 Mar 2019 20:31:17 +0100
Subject: [PATCH 39/66] [Configuration] Bump version to dev.2019-03-17
---
lib/Configuration.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/Configuration.php b/lib/Configuration.php
index 7d3a0f50..2b736119 100644
--- a/lib/Configuration.php
+++ b/lib/Configuration.php
@@ -28,7 +28,7 @@ final class Configuration {
*
* @todo Replace this property by a constant.
*/
- public static $VERSION = '2019-03-17';
+ public static $VERSION = 'dev.2019-03-17';
/**
* Holds the configuration data.
From 684558e276af9af6858adfa6d9d5d0e5bf964bb6 Mon Sep 17 00:00:00 2001
From: Nemo
Date: Mon, 18 Mar 2019 01:10:21 +0530
Subject: [PATCH 40/66] [StockFilingsBridge] Add new bridge (#1011)
---
bridges/StockFilingsBridge.php | 80 ++++++++++++++++++++++++++++++++++
lib/FeedItem.php | 2 +-
2 files changed, 81 insertions(+), 1 deletion(-)
create mode 100644 bridges/StockFilingsBridge.php
diff --git a/bridges/StockFilingsBridge.php b/bridges/StockFilingsBridge.php
new file mode 100644
index 00000000..f774244a
--- /dev/null
+++ b/bridges/StockFilingsBridge.php
@@ -0,0 +1,80 @@
+ array(
+ 'name' => 'cik',
+ 'required' => true,
+ 'exampleValue' => 'AMD',
+ // https://stackoverflow.com/a/12827734
+ 'pattern' => '[A-Za-z0-9]+',
+ ),
+ ));
+
+ public function getIcon() {
+ return 'https://www.sec.gov/favicon.ico';
+ }
+
+ /**
+ * Generates search URL
+ */
+ private function getSearchUrl() {
+ return self::SEARCH_URL . $this->getInput('ticker');
+ }
+
+ /**
+ * Returns the Company Name
+ */
+ private function getRssFeed($html) {
+ $links = $html->find('#contentDiv a');
+
+ foreach ($links as $link) {
+ $href = $link->href;
+
+ if (substr($href, 0, 4) !== 'http') {
+ $href = self::WEBSITE_ROOT . $href;
+ }
+ parse_str(html_entity_decode(parse_url($href, PHP_URL_QUERY)), $query);
+
+ if (isset($query['output']) and ($query['output'] == 'atom')) {
+ return $href;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Return \simple_html_dom object
+ * for the entire html of the product page
+ */
+ private function getHtml() {
+ $uri = $this->getSearchUrl();
+
+ return getSimpleHTMLDOM($uri) ?: returnServerError('Could not request SEC.');
+ }
+
+ /**
+ * Scrape the SEC Stock Filings RSS Feed URL
+ * and redirect there
+ */
+ public function collectData() {
+ $html = $this->getHtml();
+ $rssFeedUrl = $this->getRssFeed($html);
+
+ if ($rssFeedUrl) {
+ parent::collectExpandableDatas($rssFeedUrl);
+ } else {
+ returnClientError('Could not find RSS Feed URL. Are you sure you used a valid CIK?');
+ }
+ }
+}
diff --git a/lib/FeedItem.php b/lib/FeedItem.php
index aedf615a..de707f8b 100644
--- a/lib/FeedItem.php
+++ b/lib/FeedItem.php
@@ -76,7 +76,7 @@ class FeedItem {
* $item['uri'] = 'https://www.github.com/rss-bridge/rss-bridge/';
* $item['title'] = 'Title';
* $item['timestamp'] = strtotime('now');
- * $item['autor'] = 'Unknown author';
+ * $item['author'] = 'Unknown author';
* $item['content'] = 'Hello World!';
* $item['enclosures'] = array('https://github.com/favicon.ico');
* $item['categories'] = array('php', 'rss-bridge', 'awesome');
From 88aae6fd9501f359b4ef4fc9c0cc224bebc47039 Mon Sep 17 00:00:00 2001
From: logmanoriginal
Date: Wed, 20 Mar 2019 17:59:16 +0100
Subject: [PATCH 41/66] core: Apply changes to fix broken Travis builds
Travis-CI recently got updated, which causes existing builds to fail.
For example: https://travis-ci.org/RSS-Bridge/rss-bridge/builds/507568117
Indenting multi-line arguments of functions fixes it.
---
lib/contents.php | 36 +++++++++++++++++++-----------------
lib/html.php | 7 ++++---
2 files changed, 23 insertions(+), 20 deletions(-)
diff --git a/lib/contents.php b/lib/contents.php
index 976ecbee..4740f5c2 100644
--- a/lib/contents.php
+++ b/lib/contents.php
@@ -207,14 +207,15 @@ EOD
* @return string Contents as simplehtmldom object.
*/
function getSimpleHTMLDOM($url,
-$header = array(),
-$opts = array(),
-$lowercase = true,
-$forceTagsClosed = true,
-$target_charset = DEFAULT_TARGET_CHARSET,
-$stripRN = true,
-$defaultBRText = DEFAULT_BR_TEXT,
-$defaultSpanText = DEFAULT_SPAN_TEXT){
+ $header = array(),
+ $opts = array(),
+ $lowercase = true,
+ $forceTagsClosed = true,
+ $target_charset = DEFAULT_TARGET_CHARSET,
+ $stripRN = true,
+ $defaultBRText = DEFAULT_BR_TEXT,
+ $defaultSpanText = DEFAULT_SPAN_TEXT){
+
$content = getContents($url, $header, $opts);
return str_get_html($content,
$lowercase,
@@ -256,15 +257,16 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
* @return string Contents as simplehtmldom object.
*/
function getSimpleHTMLDOMCached($url,
-$duration = 86400,
-$header = array(),
-$opts = array(),
-$lowercase = true,
-$forceTagsClosed = true,
-$target_charset = DEFAULT_TARGET_CHARSET,
-$stripRN = true,
-$defaultBRText = DEFAULT_BR_TEXT,
-$defaultSpanText = DEFAULT_SPAN_TEXT){
+ $duration = 86400,
+ $header = array(),
+ $opts = array(),
+ $lowercase = true,
+ $forceTagsClosed = true,
+ $target_charset = DEFAULT_TARGET_CHARSET,
+ $stripRN = true,
+ $defaultBRText = DEFAULT_BR_TEXT,
+ $defaultSpanText = DEFAULT_SPAN_TEXT){
+
Debug::log('Caching url ' . $url . ', duration ' . $duration);
// Initialize cache
diff --git a/lib/html.php b/lib/html.php
index e49ca7af..0778c640 100644
--- a/lib/html.php
+++ b/lib/html.php
@@ -26,9 +26,10 @@
* already removes some of the tags (search for `remove_noise` in simple_html_dom.php).
*/
function sanitize($html,
-$tags_to_remove = array('script', 'iframe', 'input', 'form'),
-$attributes_to_keep = array('title', 'href', 'src'),
-$text_to_keep = array()){
+ $tags_to_remove = array('script', 'iframe', 'input', 'form'),
+ $attributes_to_keep = array('title', 'href', 'src'),
+ $text_to_keep = array()){
+
$htmlContent = str_get_html($html);
/*
From 835af1faf1ba252496f3d4a7bc6c1230f1ee010c Mon Sep 17 00:00:00 2001
From: logmanoriginal
Date: Wed, 20 Mar 2019 19:27:27 +0100
Subject: [PATCH 42/66] travis: Update build script to test more reasonable
configurations
PHP nightly recently got updated to dev-8.x, which is not supported
by any of the test scripts. This makes the test pretty useless and
doesn't help in any way.
Instead, the build script should focus on current versions of PHP,
starting from 5.6 to 7.3 (current stable release).
PHP 7.3 is a reasonable version to use for finding breaking changes
in the test scripts (phpunit especially warns about changes). These
tests can fail, of course.
---
.travis.yml | 42 ++++++++++++++++++++++--------------------
1 file changed, 22 insertions(+), 20 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 55210788..841ac5db 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,41 +4,43 @@ language: php
install:
- composer global require dealerdirect/phpcodesniffer-composer-installer;
- composer global require phpcompatibility/php-compatibility;
- # Use PHPUnit 6 for unit tests (stable), requires PHP 7
- - if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
- composer global require phpunit/phpunit ^6;
- fi
- # Use latest PHPUnit on nightly to detect breaking changes
- - if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then
- composer global require phpunit/phpunit;
+ - if [[ "$PHPUNIT" ]]; then
+ composer global require phpunit/phpunit ^$PHPUNIT;
fi
script:
- phpenv rehash
# Run PHP_CodeSniffer on all versions
- ~/.config/composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
- # Check PHP compatibility for the lowest supported version
- - if [[ $TRAVIS_PHP_VERSION == "5.6" ]]; then
+ # Check PHP compatibility for the lowest and highest supported version
+ - if [[ $TRAVIS_PHP_VERSION == "5.6" || $TRAVIS_PHP_VERSION == "7.3" ]]; then
~/.config/composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --extensions=php -p;
fi
- # Run unit tests (stable)
- - if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
- phpunit --configuration=phpunit.xml --include-path=lib/;
- fi
- # Run unit tests (latest/nightly)
- # Check PHP compatibility for all versions, starting at the lowest supported version in order to detect breaking changes
- - if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then
- phpunit --configuration=phpunit.xml --include-path=lib/;
- ~/.config/composer/vendor/bin/phpcs . --standard=PHPCompatibility --extensions=php -p --runtime-set testVersion 5.6-;
+ # Run unit tests on highest major version
+ - if [[ ${TRAVIS_PHP_VERSION:0:1} == "7" ]]; then
+ ~/.config/composer/vendor/bin/phpunit --configuration=phpunit.xml --include-path=lib/;
fi
+php:
+ - 7.3
+
+env:
+ - PHPUNIT=6
+ - PHPUNIT=7
+ - PHPUNIT=8
+
matrix:
fast_finish: true
include:
- php: 5.6
+ env: PHPUNIT=
- php: 7.0
- - php: nightly
+ - php: 7.1
+ - php: 7.2
allow_failures:
- - php: nightly
+ - php: 7.3
+ env: PHPUNIT=7
+ - php: 7.3
+ env: PHPUNIT=8
From 6293c3d33d6815f669e42719ba65d78422647f78 Mon Sep 17 00:00:00 2001
From: logmanoriginal
Date: Thu, 21 Mar 2019 19:42:44 +0100
Subject: [PATCH 43/66] [FeedItem] Filter duplicate enclosures
---
lib/FeedItem.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/FeedItem.php b/lib/FeedItem.php
index de707f8b..b0095be2 100644
--- a/lib/FeedItem.php
+++ b/lib/FeedItem.php
@@ -347,7 +347,7 @@ class FeedItem {
$enclosure,
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
Debug::log('Each enclosure must contain a scheme, host and path!');
- } else {
+ } elseif(!in_array($enclosure, $this->enclosures)) {
$this->enclosures[] = $enclosure;
}
}
From 18d5ef192c0d1cb20dad01bd3fcb9719ca886b38 Mon Sep 17 00:00:00 2001
From: Xurxo Fresco
Date: Fri, 22 Mar 2019 21:33:46 +0100
Subject: [PATCH 44/66] [IvooxBridge] Add new bridge (#597)
---
bridges/IvooxBridge.php | 128 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 128 insertions(+)
create mode 100644 bridges/IvooxBridge.php
diff --git a/bridges/IvooxBridge.php b/bridges/IvooxBridge.php
new file mode 100644
index 00000000..3cdf74bc
--- /dev/null
+++ b/bridges/IvooxBridge.php
@@ -0,0 +1,128 @@
+ array(
+ 's' => array(
+ 'name' => 'keyword',
+ 'exampleValue' => 'test'
+ )
+ )
+ );
+
+ private function ivBridgeAddItem(
+ $episode_link,
+ $podcast_name,
+ $episode_title,
+ $author_name,
+ $episode_description,
+ $publication_date,
+ $episode_duration) {
+ $item = array();
+ $item['title'] = htmlspecialchars_decode($podcast_name . ': ' . $episode_title);
+ $item['author'] = $author_name;
+ $item['timestamp'] = $publication_date;
+ $item['uri'] = $episode_link;
+ $item['content'] = '' . $podcast_name . ': ' . $episode_title
+ . '
Duration: ' . $episode_duration
+ . '
Description:
' . $episode_description;
+ $this->items[] = $item;
+ }
+
+ private function ivBridgeParseHtmlListing($html) {
+ $limit = 4;
+ $count = 0;
+
+ foreach($html->find('div.flip-container') as $flipper) {
+ $linkcount = 0;
+ if(!empty($flipper->find( 'div.modulo-type-banner' ))) {
+ // ad
+ continue;
+ }
+
+ if($count < $limit) {
+ foreach($flipper->find('div.header-modulo') as $element) {
+ foreach($element->find('a') as $link) {
+ if ($linkcount == 0) {
+ $episode_link = $link->href;
+ $episode_title = $link->title;
+ } elseif ($linkcount == 1) {
+ $author_link = $link->href;
+ $author_name = $link->title;
+ } elseif ($linkcount == 2) {
+ $podcast_link = $link->href;
+ $podcast_name = $link->title;
+ }
+
+ $linkcount++;
+ }
+ }
+
+ $episode_description = $flipper->find('button.btn-link', 0)->getAttribute('data-content');
+ $episode_duration = $flipper->find('p.time', 0)->innertext;
+ $publication_date = $flipper->find('li.date', 0)->getAttribute('title');
+
+ // alternative date_parse_from_format
+ // or DateTime::createFromFormat('G:i - d \d\e M \d\e Y', $publication);
+ // TODO: month name translations, due function doesn't support locale
+
+ $a = strptime($publication_date, '%H:%M - %d de %b. de %Y'); // obsolete function, uses c libraries
+ $publication_date = mktime(0, 0, 0, $a['tm_mon'] + 1, $a['tm_mday'], $a['tm_year'] + 1900);
+
+ $this->ivBridgeAddItem(
+ $episode_link,
+ $podcast_name,
+ $episode_title,
+ $author_name,
+ $episode_description,
+ $publication_date,
+ $episode_duration
+ );
+ $count++;
+ }
+ }
+ }
+
+ public function collectData() {
+
+ // store locale, change to spanish
+ $originalLocales = explode(';', setlocale(LC_ALL, 0));
+ setlocale(LC_ALL, 'es_ES.utf8');
+
+ $xml = '';
+ $html = '';
+ $url_feed = '';
+ if($this->getInput('s')) { /* Search modes */
+ $this->request = str_replace(' ', '-', $this->getInput('s'));
+ $url_feed = self::URI . urlencode($this->request) . '_sb_f_1.html?o=uploaddate';
+ } else {
+ returnClientError('Not valid mode at IvooxBridge');
+ }
+
+ $dom = getSimpleHTMLDOM($url_feed)
+ or returnServerError('Could not request ' . $url_feed);
+ $this->ivBridgeParseHtmlListing($dom);
+
+ // restore locale
+
+ foreach($originalLocales as $localeSetting) {
+ if(strpos($localeSetting, '=') !== false) {
+ list($category, $locale) = explode('=', $localeSetting);
+ } else {
+ $category = LC_ALL;
+ $locale = $localeSetting;
+ }
+
+ setlocale($category, $locale);
+ }
+ }
+}
From 281eaacaeb769e9b3a97e81953a4b6c4e8a0b52f Mon Sep 17 00:00:00 2001
From: Dreckiger-Dan
Date: Sat, 23 Mar 2019 16:22:44 +0100
Subject: [PATCH 45/66] [HeiseBridge] Add new bridge (#744)
---
bridges/HeiseBridge.php | 75 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
create mode 100644 bridges/HeiseBridge.php
diff --git a/bridges/HeiseBridge.php b/bridges/HeiseBridge.php
new file mode 100644
index 00000000..1d9d8025
--- /dev/null
+++ b/bridges/HeiseBridge.php
@@ -0,0 +1,75 @@
+ array(
+ 'name' => 'Category',
+ 'type' => 'list',
+ 'values' => array(
+ 'Alle News'
+ => 'https://www.heise.de/newsticker/heise-atom.xml',
+ 'Top-News'
+ => 'https://www.heise.de/newsticker/heise-top-atom.xml',
+ 'Internet-Störungen'
+ => 'https://www.heise.de/netze/netzwerk-tools/imonitor-internet-stoerungen/feed/aktuelle-meldungen/',
+ 'Alle News von heise Developer'
+ => 'https://www.heise.de/developer/rss/news-atom.xml'
+ )
+ ),
+ 'limit' => array(
+ 'name' => 'Limit',
+ 'type' => 'number',
+ 'required' => false,
+ 'title' => 'Specify number of full articles to return',
+ 'defaultValue' => 5
+ )
+ ));
+ const LIMIT = 5;
+
+ public function collectData() {
+ $this->collectExpandableDatas(
+ $this->getInput('category'),
+ $this->getInput('limit') ?: static::LIMIT
+ );
+ }
+
+ protected function parseItem($feedItem) {
+ $item = parent::parseItem($feedItem);
+ $uri = $item['uri'];
+
+ do {
+ $article = getSimpleHTMLDOMCached($uri)
+ or returnServerError('Could not open article: ' . $uri);
+
+ $article = defaultLinkTo($article, $uri);
+ $item = $this->addArticleToItem($item, $article);
+
+ if($next = $article->find('.pagination a[rel="next"]', 0))
+ $uri = $next->href;
+ } while ($next);
+
+ return $item;
+ }
+
+ private function addArticleToItem($item, $article) {
+ if($author = $article->find('[itemprop="author"]', 0))
+ $item['author'] = $author->plaintext;
+
+ $content = $article->find('div[class*="article-content"]', 0);
+
+ foreach($content->find('p, h3, ul, table, pre, img') as $element) {
+ $item['content'] .= $element;
+ }
+
+ foreach($content->find('img') as $img) {
+ $item['enclosures'][] = $img->src;
+ }
+
+ return $item;
+ }
+}
From 3212156925534411b3ab61beeec0d780303b31d1 Mon Sep 17 00:00:00 2001
From: Antoine Turmel
Date: Sat, 23 Mar 2019 16:27:07 +0100
Subject: [PATCH 46/66] [MozillaBugTrackerBridge] New Bridge (#916)
This Bridge is a clone of KernelBugTrackerBridge but for Mozilla Bugzilla. There is some difference in the class used to get the right comments.
---
bridges/MozillaBugTrackerBridge.php | 154 ++++++++++++++++++++++++++++
1 file changed, 154 insertions(+)
create mode 100644 bridges/MozillaBugTrackerBridge.php
diff --git a/bridges/MozillaBugTrackerBridge.php b/bridges/MozillaBugTrackerBridge.php
new file mode 100644
index 00000000..375912de
--- /dev/null
+++ b/bridges/MozillaBugTrackerBridge.php
@@ -0,0 +1,154 @@
+ array(
+ 'id' => array(
+ 'name' => 'Bug tracking ID',
+ 'type' => 'number',
+ 'required' => true,
+ 'title' => 'Insert bug tracking ID',
+ 'exampleValue' => 121241
+ ),
+ 'limit' => array(
+ 'name' => 'Number of comments to return',
+ 'type' => 'number',
+ 'required' => false,
+ 'title' => 'Specify number of comments to return',
+ 'defaultValue' => -1
+ ),
+ 'sorting' => array(
+ 'name' => 'Sorting',
+ 'type' => 'list',
+ 'required' => false,
+ 'title' => 'Defines the sorting order of the comments returned',
+ 'defaultValue' => 'of',
+ 'values' => array(
+ 'Oldest first' => 'of',
+ 'Latest first' => 'lf'
+ )
+ )
+ )
+ );
+
+ private $bugid = '';
+ private $bugdesc = '';
+
+ public function getIcon() {
+ return self::URI . '/extensions/BMO/web/images/favicon.ico';
+ }
+
+ public function collectData(){
+ $limit = $this->getInput('limit');
+ $sorting = $this->getInput('sorting');
+
+ // We use the print preview page for simplicity
+ $html = getSimpleHTMLDOMCached($this->getURI() . '&format=multiple',
+ 86400,
+ null,
+ null,
+ true,
+ true,
+ DEFAULT_TARGET_CHARSET,
+ false, // Do NOT remove line breaks
+ DEFAULT_BR_TEXT,
+ DEFAULT_SPAN_TEXT);
+
+ if($html === false)
+ returnServerError('Failed to load page!');
+
+ // Store header information into private members
+ $this->bugid = $html->find('#bugzilla-body', 0)->find('a', 0)->innertext;
+ $this->bugdesc = $html->find('table.bugfields', 0)->find('tr', 0)->find('td', 0)->innertext;
+
+ // Get and limit comments
+ $comments = $html->find('.bz_comment_table div.bz_comment');
+
+ if($limit > 0 && count($comments) > $limit) {
+ $comments = array_slice($comments, count($comments) - $limit, $limit);
+ }
+
+ // Order comments
+ switch($sorting) {
+ case 'lf': $comments = array_reverse($comments, true);
+ case 'of':
+ default: // Nothing to do, keep original order
+ }
+
+ foreach($comments as $comment) {
+ $comment = $this->inlineStyles($comment);
+
+ $item = array();
+ $item['uri'] = $this->getURI() . '#' . $comment->id;
+ $item['author'] = $comment->find('span.bz_comment_user', 0)->innertext;
+ $item['title'] = $comment->find('span.bz_comment_number', 0)->find('a', 0)->innertext;
+ $item['timestamp'] = strtotime($comment->find('span.bz_comment_time', 0)->innertext);
+ $item['content'] = $comment->find('pre.bz_comment_text', 0)->innertext;
+
+ // Fix line breaks (they use LF)
+ $item['content'] = str_replace("\n", '
', $item['content']);
+
+ // Fix relative URIs
+ $item['content'] = $this->replaceRelativeURI($item['content']);
+
+ $this->items[] = $item;
+ }
+
+ }
+
+ public function getURI(){
+ switch($this->queriedContext) {
+ case 'Bug comments':
+ return parent::getURI()
+ . '/show_bug.cgi?id='
+ . $this->getInput('id');
+ break;
+ default: return parent::getURI();
+ }
+ }
+
+ public function getName(){
+ switch($this->queriedContext) {
+ case 'Bug comments':
+ return 'Bug '
+ . $this->bugid
+ . ' tracker for '
+ . $this->bugdesc
+ . ' - '
+ . parent::getName();
+ break;
+ default: return parent::getName();
+ }
+ }
+
+ /**
+ * Replaces all relative URIs with absolute ones
+ *
+ * @param string $content The source string
+ * @return string Returns the source string with all relative URIs replaced
+ * by absolute ones.
+ */
+ private function replaceRelativeURI($content){
+ return preg_replace('/href="(?!http)/', 'href="' . self::URI . '/', $content);
+ }
+
+ /**
+ * Adds styles as attributes to tags with known classes
+ *
+ * @param object $html A simplehtmldom object
+ * @return object Returns the original object with styles added as
+ * attributes.
+ */
+ private function inlineStyles($html){
+ foreach($html->find('.bz_obsolete') as $element) {
+ $element->style = 'text-decoration:line-through;';
+ }
+
+ return $html;
+ }
+
+}
From 835e3b11638f9106fa75c71b0fe2359b7b71eb8b Mon Sep 17 00:00:00 2001
From: logmanoriginal
Date: Sat, 23 Mar 2019 16:30:15 +0100
Subject: [PATCH 47/66] [MozillaBugTrackerBridge] Fix typo
---
bridges/MozillaBugTrackerBridge.php | 1 -
1 file changed, 1 deletion(-)
diff --git a/bridges/MozillaBugTrackerBridge.php b/bridges/MozillaBugTrackerBridge.php
index 375912de..356bedcf 100644
--- a/bridges/MozillaBugTrackerBridge.php
+++ b/bridges/MozillaBugTrackerBridge.php
@@ -150,5 +150,4 @@ class MozillaBugTrackerBridge extends BridgeAbstract {
return $html;
}
-
}
From b9bbc9bdda83c212f026c45c235c437f2114a202 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ale=C5=9B=20Bu=C5=82oj=C4=8Dyk?=
Date: Sat, 23 Mar 2019 18:39:09 +0300
Subject: [PATCH 48/66] [FacebookBridge] Fix decoding of cyrillic letters in
group names (#842)
---
bridges/FacebookBridge.php | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/bridges/FacebookBridge.php b/bridges/FacebookBridge.php
index 7b617057..c0901072 100644
--- a/bridges/FacebookBridge.php
+++ b/bridges/FacebookBridge.php
@@ -219,8 +219,7 @@ class FacebookBridge extends BridgeAbstract {
$ogtitle = $html->find('meta[property="og:title"]', 0)
or returnServerError('Unable to find group title!');
- return htmlspecialchars_decode($ogtitle->content, ENT_QUOTES);
-
+ return html_entity_decode($ogtitle->content, ENT_QUOTES);
}
private function extractGroupURI($post) {
From b6943de0cab856745fa9dc27b03c24fe3e07009f Mon Sep 17 00:00:00 2001
From: DnAp
Date: Sat, 23 Mar 2019 17:40:19 +0200
Subject: [PATCH 49/66] [BingSearch] Add new bridge (#1046)
---
bridges/BingSearchBridge.php | 119 +++++++++++++++++++++++++++++++++++
1 file changed, 119 insertions(+)
create mode 100644 bridges/BingSearchBridge.php
diff --git a/bridges/BingSearchBridge.php b/bridges/BingSearchBridge.php
new file mode 100644
index 00000000..ccfffdf9
--- /dev/null
+++ b/bridges/BingSearchBridge.php
@@ -0,0 +1,119 @@
+ array(
+ 'category' => array(
+ 'name' => 'Categories',
+ 'type' => 'list',
+ 'values' => self::IMAGE_DISCOVER_CATEGORIES
+ ),
+ 'image_size' => array(
+ 'name' => 'Image size',
+ 'type' => 'list',
+ 'values' => array(
+ 'Small' => 'turl',
+ 'Full size' => 'imgurl'
+ )
+ )
+ )
+ );
+
+ const IMAGE_DISCOVER_CATEGORIES = array(
+ 'Abstract' => 'abstract',
+ 'Animals' => 'animals',
+ 'Anime' => 'anime',
+ 'Architecture' => 'architecture',
+ 'Arts and Crafts' => 'arts-and-crafts',
+ 'Beauty' => 'beauty',
+ 'Cars and Motorcycles' => 'cars-and-motorcycles',
+ 'Cats' => 'cats',
+ 'Celebrities' => 'celebrities',
+ 'Comics' => 'comics',
+ 'DIY' => 'diy',
+ 'Dogs' => 'dogs',
+ 'Fitness' => 'fitness',
+ 'Food and Drink' => 'food-and-drink',
+ 'Funny' => 'funny',
+ 'Gadgets' => 'gadgets',
+ 'Gardening' => 'gardening',
+ 'Geeky' => 'geeky',
+ 'Hairstyles' => 'hairstyles',
+ 'Home Decor' => 'home-decor',
+ 'Marine Life' => 'marine-life',
+ 'Men\'s Fashion' => 'men%27s-fashion',
+ 'Nature' => 'nature',
+ 'Outdoors' => 'outdoors',
+ 'Parenting' => 'parenting',
+ 'Phone Wallpapers' => 'phone-wallpapers',
+ 'Photography' => 'photography',
+ 'Quotes' => 'quotes',
+ 'Recipes' => 'recipes',
+ 'Snow' => 'snow',
+ 'Tattoos' => 'tattoos',
+ 'Travel' => 'travel',
+ 'Video Games' => 'video-games',
+ 'Weddings' => 'weddings',
+ 'Women\'s Fashion' => 'women%27s-fashion',
+ );
+
+ public function getIcon()
+ {
+ return 'https://www.bing.com/sa/simg/bing_p_rr_teal_min.ico';
+ }
+
+ public function collectData()
+ {
+ $this->items = $this->imageDiscover($this->getInput('category'));
+ }
+
+ public function getName()
+ {
+ if ($this->getInput('category')) {
+ if (isset(self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')])) {
+ $category = self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')];
+ } else {
+ $category = 'Unknown';
+ }
+
+ return 'Best ' . $category . ' - Bing Image Discover';
+ }
+ return parent::getName();
+ }
+
+ private function imageDiscover($category)
+ {
+ $html = getSimpleHTMLDOM(self::URI . '/discover/' . $category)
+ or returnServerError('Could not request ' . self::NAME);
+ $sizeKey = $this->getInput('image_size');
+
+ $items = [];
+ foreach ($html->find('a.iusc') as $element) {
+ $data = json_decode(htmlspecialchars_decode($element->getAttribute('m')), true);
+
+ $item = array();
+ $item['title'] = basename(rtrim($data['imgurl'], '/'));
+ $item['uri'] = $data['imgurl'];
+ $item['content'] = '
+
+ Source:
';
+ $item['enclosures'] = $data['imgurl'];
+
+ $items[] = $item;
+ }
+ return $items;
+ }
+
+ private function curUrl($url)
+ {
+ if (strlen($url) <= 80) {
+ return $url;
+ }
+ return substr($url, 0, 80) . '...';
+ }
+}
From 291e8c2a2374a6f7ac34c352295bc0fdccba4992 Mon Sep 17 00:00:00 2001
From: sysadminstory
Date: Thu, 4 Apr 2019 22:39:39 +0200
Subject: [PATCH 50/66] [AutoJMBridge] Fix bridge after website change
(#1081)
* [AutoJMBridge] Fix bridge after website change
The website was totally reworked, so the bridge had to be reworked too.
The bridge parameters changed, therefore old RSS feed will not work
anymore, but it was impossible to do it in another way.
---
bridges/AutoJMBridge.php | 194 +++++++++++++++++++++++++++++++--------
1 file changed, 157 insertions(+), 37 deletions(-)
diff --git a/bridges/AutoJMBridge.php b/bridges/AutoJMBridge.php
index 598f0431..091d64a1 100644
--- a/bridges/AutoJMBridge.php
+++ b/bridges/AutoJMBridge.php
@@ -3,63 +3,183 @@
class AutoJMBridge extends BridgeAbstract {
const NAME = 'AutoJM';
- const URI = 'http://www.autojm.fr/';
+ const URI = 'https://www.autojm.fr/';
const DESCRIPTION = 'Suivre les offres de véhicules proposés par AutoJM en fonction des critères de filtrages';
const MAINTAINER = 'sysadminstory';
const PARAMETERS = array(
'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM' => array(
'url' => array(
- 'name' => 'URL de la recherche',
+ 'name' => 'URL du modèle',
'type' => 'text',
'required' => true,
'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/',
- 'exampleValue' => 'gammes/index/398?order_by=finition_asc&energie[]=3&transmission[]=2&dispo=all'
+ 'exampleValue' => 'achat-voitures-neuves-peugeot-nouvelle-308-5p'
+ ),
+ 'isDispo' => array(
+ 'name' => 'Disponibilité',
+ 'type' => 'list',
+ 'values' => array(
+ '-' => '',
+ 'En stock' => 1,
+ 'Sur commande' => 0
+ ),
+ 'title' => 'Critère de disponibilité'
+ ),
+ 'energy' => array(
+ 'name' => 'Carburant',
+ 'type' => 'list',
+ 'values' => array(
+ '-' => '',
+ 'Diesel' => 1,
+ 'Essence' => 3,
+ 'Hybride' => 5
+ ),
+ 'title' => 'Carburant'
+ ),
+ 'transmission' => array(
+ 'name' => 'Transmission',
+ 'type' => 'list',
+ 'values' => array(
+ '-' => '',
+ 'Automatique' => 1,
+ 'Manuelle' => 2
+ ),
+ 'title' => 'Transmission'
+ ),
+ 'priceMin' => array(
+ 'name' => 'Prix minimum',
+ 'type' => 'number',
+ 'required' => false,
+ 'title' => 'Prix minimum du véhicule',
+ 'exampleValue' => '10000',
+ 'defaultValue' => '0'
+ ),
+ 'priceMax' => array(
+ 'name' => 'Prix maximum',
+ 'type' => 'number',
+ 'required' => false,
+ 'title' => 'Prix maximum du véhicule',
+ 'exampleValue' => '15000',
+ 'defaultValue' => '150000'
)
)
);
const CACHE_TIMEOUT = 3600;
public function getIcon() {
- return self::URI . 'assets/images/favicon.ico';
+ return self::URI . 'favicon.ico';
}
public function collectData() {
- $html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
+
+ $model_url = self::URI . $this->getInput('url');
+
+ // Get the session cookies and the form token
+ $this->getInitialParameters($model_url);
+
+ // Build the form
+ $post_data = array(
+ 'form[isDispo]' => $this->getInput('isDispo'),
+ 'form[energy]' => $this->getInput('energy'),
+ 'form[transmission]' => $this->getInput('transmission'),
+ 'form[priceMin]' => $this->getInput('priceMin'),
+ 'form[priceMin]' => $this->getInput('priceMin'),
+ 'form[_token]' => $this->token
+ );
+
+ // Set the Form request content type
+ $header = array(
+ 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8',
+ );
+
+ // Set the curl options (POST query and content, and session cookies
+ $curl_opts = array(
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => http_build_query($post_data),
+ CURLOPT_COOKIE => $this->cookies
+ );
+
+ // Get the JSON content of the form
+ $json = getContents($model_url, $header, $curl_opts)
or returnServerError('Could not request AutoJM.');
- $list = $html->find('div[class*=ligne_modele]');
- foreach($list as $element) {
- $image = $element->find('img[class=width-100]', 0)->src;
- $serie = $element->find('div[class=serie]', 0)->find('span', 0)->plaintext;
- $url = $element->find('div[class=serie]', 0)->find('a[class=btn_ligne color-black]', 0)->href;
- if($element->find('div[class*=hasStock-info]', 0) != null) {
- $dispo = 'Disponible';
- } else {
- $dispo = 'Sur commande';
+
+ // Extract the HTML content from the JSON result
+ $data = json_decode($json);
+ $html = str_get_html($data->content);
+
+ // Go through every finisha of the model
+ $list = $html->find('h2');
+ foreach ($list as $finish) {
+ $finish_name = $finish->plaintext;
+ $motorizations = $finish->next_sibling()->find('li');
+ foreach ($motorizations as $element) {
+ $image = $element->find('div[class=block-product-image]', 0)->{'data-ga-banner'};
+ $serie = $element->find('span[class=model]', 0)->plaintext;
+ $url = self::URI . substr($element->find('a', 0)->href, 1);
+ if ($element->find('span[class*=block-product-nbModel]', 0) != null) {
+ $availability = 'En Stock';
+ } else {
+ $availability = 'Sur commande';
+ }
+ $discount_html = $element->find('span[class*=tag--promo]', 0);
+ if ($discount_html != null) {
+ $discount = $discount_html->plaintext;
+ } else {
+ $discount = 'inconnue';
+ }
+ $price = $element->find('span[class=price red h1]', 0)->plaintext;
+ $item = array();
+ $item['title'] = $finish_name . ' ' . $serie;
+ $item['content'] = ''
+ . $finish_name . ' ' . $serie . '
';
+ $item['content'] .= '- Disponibilité : ' . $availability . '
';
+ $item['content'] .= '- Série : ' . $serie . '
';
+ $item['content'] .= '- Remise : ' . $discount . '
';
+ $item['content'] .= '- Prix : ' . $price . '
';
+
+ // Add a fictionnal anchor to the RSS element URL, based on the item content ;
+ // As the URL could be identical even if the price change, some RSS reader will not show those offers as new items
+ $item['uri'] = $url . '#' . md5($item['content']);
+
+ $this->items[] = $item;
}
- $carburant = str_replace('dispo |', '', $element->find('div[class=carburant]', 0)->plaintext);
- $transmission = $element->find('div[class*=bv]', 0)->plaintext;
- $places = $element->find('div[class*=places]', 0)->plaintext;
- $portes = $element->find('div[class*=nb_portes]', 0)->plaintext;
- $carosserie = $element->find('div[class*=coloris]', 0)->plaintext;
- $remise = $element->find('div[class*=remise]', 0)->plaintext;
- $prix = $element->find('div[class*=prixjm]', 0)->plaintext;
-
- $item = array();
- $item['uri'] = $url;
- $item['title'] = $serie;
- $item['content'] = '' . $serie . '
';
- $item['content'] .= '- Disponibilité : ' . $dispo . '
';
- $item['content'] .= '- Carburant : ' . $carburant . '
';
- $item['content'] .= '- Transmission : ' . $transmission . '
';
- $item['content'] .= '- Nombre de places : ' . $places . '
';
- $item['content'] .= '- Nombre de portes : ' . $portes . '
';
- $item['content'] .= '- Série : ' . $serie . '
';
- $item['content'] .= '- Carosserie : ' . $carosserie . '
';
- $item['content'] .= '- Remise : ' . $remise . '
';
- $item['content'] .= '- Prix : ' . $prix . '
';
-
- $this->items[] = $item;
}
+ }
+
+ /**
+ * Gets the session cookie and the form token
+ *
+ * @param string $pageURL The URL from which to get the values
+ */
+ private function getInitialParameters($pageURL) {
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $pageURL);
+ curl_setopt($ch, CURLOPT_HEADER, true);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ $data = curl_exec($ch);
+
+ // Separate the response header and the content
+ $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+ $header = substr($data, 0, $headerSize);
+ $content = substr($data, $headerSize);
+ curl_close($ch);
+
+ // Extract the cookies from the headers
+ $cookies = '';
+ $http_response_header = explode("\r\n", $header);
+ foreach ($http_response_header as $hdr) {
+ if (strpos($hdr, 'Set-Cookie') !== false) {
+ $cLine = explode(':', $hdr)[1];
+ $cLine = explode(';', $cLine)[0];
+ $cookies .= ';' . $cLine;
+ }
+ }
+ $this->cookies = trim(substr($cookies, 1));
+
+ // Get the token from the content
+ $html = str_get_html($content);
+ $token = $html->find('input[type=hidden][id=form__token]', 0);
+ $this->token = $token->value;
}
}
From 966d450d2703a51028deaa79eb7f92d388294498 Mon Sep 17 00:00:00 2001
From: Thibault Couraud <1036233+couraudt@users.noreply.github.com>
Date: Thu, 4 Apr 2019 20:44:44 +0000
Subject: [PATCH 51/66] [FindACrew] Update bridge according new findacrew.net
website (#1080)
* update bridge according new crewbay.com website
---
bridges/FindACrewBridge.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/bridges/FindACrewBridge.php b/bridges/FindACrewBridge.php
index c245c84a..1dac775a 100644
--- a/bridges/FindACrewBridge.php
+++ b/bridges/FindACrewBridge.php
@@ -62,10 +62,10 @@ class FindACrewBridge extends BridgeAbstract {
foreach ($annonces as $annonce) {
$item = array();
- $img = parent::getURI() . $annonce->find('.css_LstPic img', 0)->getAttribute('src');
- $item['title'] = $annonce->find('.css_LstCtrls span', 0)->plaintext;
- $item['uri'] = parent::getURI() . $annonce->find('.css_PnlCtrls a', 0)->href;
- $content = $annonce->find('.css_LstDtl div', 2)->innertext;
+ $img = parent::getURI() . $annonce->find('.lst-pic img', 0)->getAttribute('src');
+ $item['title'] = $annonce->find('.lst-tags span', 0)->plaintext;
+ $item['uri'] = parent::getURI() . $annonce->find('.lst-ctrls a', 0)->href;
+ $content = $annonce->find('.lst-dtl', 0)->innertext;
$item['content'] = "
$content";
$item['enclosures'] = array($img);
$item['categories'] = array($annonce->find('.css_AccLocCur', 0)->plaintext);
From 0aa88585518269791a455fb56944099b51ae6799 Mon Sep 17 00:00:00 2001
From: Lyra
Date: Thu, 4 Apr 2019 22:45:41 +0200
Subject: [PATCH 52/66] [RoadAndTrackBridge] Generate a signature key for every
client instead of hardcoding it
---
bridges/RoadAndTrackBridge.php | 15 ++++-----------
1 file changed, 4 insertions(+), 11 deletions(-)
diff --git a/bridges/RoadAndTrackBridge.php b/bridges/RoadAndTrackBridge.php
index 15a2e5aa..f277b660 100644
--- a/bridges/RoadAndTrackBridge.php
+++ b/bridges/RoadAndTrackBridge.php
@@ -35,21 +35,14 @@ class RoadAndTrackBridge extends BridgeAbstract {
)
);
+ const API_TOKEN = '2e18e904-d9cd-4911-b30c-1817b1e0b04b';
const SIG_URL = 'https://cloud.mazdigital.com/feeds/production/comboapp/204/api/v3/';
+ const GSIG_URL = 'https://dashboard.mazsystems.com/services/cf_access?app_id=204&app_type=comboapp&api_token=';
public function collectData() {
- //Magic
- $signVal = '?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9jbG91ZC5tYXpkaWd';
- $signVal .= 'pdGFsLmNvbS9mZWVkcy9wcm9kdWN0aW9uL2NvbWJvYXBwLzIwNC8qIiwiQ29uZGl0aW9uIj';
- $signVal .= 'p7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNTUyNTU5MDUzfSwiSXBBZGRyZ';
- $signVal .= 'XNzIjp7IkFXUzpTb3VyY2VJcCI6IjAuMC4wLjAvMCJ9fX1dfQ__&Signature=jgS~Jccjs';
- $signVal .= 'lXMMywWesmwDpUbHvEmrADRP7iBRzT~OiP-O~zI-8TtQzqTP7GUrpB9~v69CvhO7-JVtw94';
- $signVal .= 'VC3N6lQrwsxTTIhpS57YGeV~MbZx~P653yUV7jb3jpJE2yUawfXnEkD-XzOIn8-caMo~14i';
- $signVal .= 'KuWV9KNDkTJaRgOMy0rrVpWqiuBjCu5s5B8Ylt2qwcpOvHjXSqG9IY5c7GUIXKsk8yXzGFi';
- $signVal .= 'yzy8hfuGgdx0n7fgl7c4-EoDgQaz~U76g0epejPxV5Csj16rCCfAqBU5kZJnACZ1vvOvRcV';
- $signVal .= 'Wiu8KUuUuCS04SPmJ73Y5XoY8~uXRScxZG1kAFTIAhT4nYVlg__&Key-Pair-Id=APKAIZB';
- $signVal .= 'QNNSW4WGIFP4Q';
+ $signVal = json_decode(getContents(self::GSIG_URL . self::API_TOKEN));
+ $signVal = $signVal->signature;
$newsElements = array();
if($this->getInput('new-cars')) {
From c9b0cd1315bbc8bcf1d85c4512b8bb0fdca8271a Mon Sep 17 00:00:00 2001
From: somini
Date: Thu, 4 Apr 2019 21:48:25 +0100
Subject: [PATCH 53/66] ComboiosDePortugalBridge: HACK: Encode the URL (#1074)
This seems like a weird bug somewhere.
Either the HTML parser should return the valid page, or the CMS should
not convert the URL first, or the URL validation regex is buggy.
---
bridges/ComboiosDePortugalBridge.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bridges/ComboiosDePortugalBridge.php b/bridges/ComboiosDePortugalBridge.php
index 4d82881f..610e23b3 100644
--- a/bridges/ComboiosDePortugalBridge.php
+++ b/bridges/ComboiosDePortugalBridge.php
@@ -14,7 +14,7 @@ class ComboiosDePortugalBridge extends BridgeAbstract {
$item = array();
$item['title'] = $element->innertext;
- $item['uri'] = self::BASE_URI . $element->href;
+ $item['uri'] = self::BASE_URI . implode('/', array_map('urlencode', explode('/', $element->href)));
$this->items[] = $item;
}
From 4ba0d8bebedd185666f2da9c860a46c5436b5100 Mon Sep 17 00:00:00 2001
From: Dreckiger-Dan
Date: Thu, 4 Apr 2019 22:50:33 +0200
Subject: [PATCH 54/66] Update .gitignore (#1078)
ignore .htaccess .htpasswd
---
.gitignore | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.gitignore b/.gitignore
index a848b5f8..d970fed9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -236,3 +236,7 @@ config.ini.php
#Builder
.buildconfig
+
+#Auth
+.htaccess
+.htpasswd
From d9ee9e272ef8d97402604dc2bfdb9b5295de0def Mon Sep 17 00:00:00 2001
From: DJCrashdummy
Date: Thu, 4 Apr 2019 22:52:59 +0200
Subject: [PATCH 55/66] [FDroidBridge] fixed bridge (#1075)
because an additional widget (i guess the language selector) was added to the homepage.
---
bridges/FDroidBridge.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/bridges/FDroidBridge.php b/bridges/FDroidBridge.php
index 65a1a9f2..7f54735a 100644
--- a/bridges/FDroidBridge.php
+++ b/bridges/FDroidBridge.php
@@ -28,14 +28,14 @@ class FDroidBridge extends BridgeAbstract {
or returnServerError('Could not request F-Droid.');
// targetting the corresponding widget based on user selection
- // "updated" is the 4th widget on the page, "added" is the 5th
+ // "updated" is the 5th widget on the page, "added" is the 6th
switch($this->getInput('u')) {
case 'updated':
- $html_widget = $html->find('div.sidebar-widget', 4);
+ $html_widget = $html->find('div.sidebar-widget', 5);
break;
default:
- $html_widget = $html->find('div.sidebar-widget', 5);
+ $html_widget = $html->find('div.sidebar-widget', 6);
break;
}
From 50c90eb5df69ea140f50068318c3794d0cdf52d5 Mon Sep 17 00:00:00 2001
From: Tobias Alexander Franke
Date: Thu, 4 Apr 2019 22:54:08 +0200
Subject: [PATCH 56/66] [EconomistBridge] Add new bridge (#1067)
* [EconomistBridge] Added new bridge
---
bridges/EconomistBridge.php | 63 +++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
create mode 100644 bridges/EconomistBridge.php
diff --git a/bridges/EconomistBridge.php b/bridges/EconomistBridge.php
new file mode 100644
index 00000000..1256be45
--- /dev/null
+++ b/bridges/EconomistBridge.php
@@ -0,0 +1,63 @@
+find('article') as $element) {
+
+ $a = $element->find('a', 0);
+ $href = self::URI . $a->href;
+ $full = getSimpleHTMLDOMCached($href);
+ $article = $full->find('article', 0);
+
+ $header = $article->find('h1', 0);
+ $author = $article->find('span[itemprop="author"]', 0);
+ $time = $article->find('time[itemprop="dateCreated"]', 0);
+ $content = $article->find('div[itemprop="description"]', 0);
+
+ // Remove newsletter subscription box
+ $newsletter = $content->find('div[class="newsletter-form__message"]', 0);
+ if ($newsletter)
+ $newsletter->outertext = '';
+
+ $newsletterForm = $content->find('form', 0);
+ if ($newsletterForm)
+ $newsletterForm->outertext = '';
+
+ // Remove next and previous article URLs at the bottom
+ $nextprev = $content->find('div[class="blog-post__next-previous-wrapper"]', 0);
+ if ($nextprev)
+ $nextprev->outertext = '';
+
+ $section = [ $article->find('h3[itemprop="articleSection"]', 0)->plaintext ];
+
+ $item = array();
+ $item['title'] = $header->find('span', 0)->innertext . ': '
+ . $header->find('span', 1)->innertext;
+
+ $item['uri'] = $href;
+ $item['timestamp'] = strtotime($time->datetime);
+ $item['author'] = $author->innertext;
+ $item['categories'] = $section;
+
+ $item['content'] = '' . $content->innertext;
+
+ $this->items[] = $item;
+
+ if (count($this->items) >= 10)
+ break;
+ }
+ }
+}
From 380fdf2e40e3bfd9e5781b44afa48499c0345045 Mon Sep 17 00:00:00 2001
From: Roliga
Date: Thu, 4 Apr 2019 22:55:46 +0200
Subject: [PATCH 57/66] [ParameterValidator] Handle missing parameter type
(#1057)
* [ParameterValidator] Handle missing parameter type
---
lib/ParameterValidator.php | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/lib/ParameterValidator.php b/lib/ParameterValidator.php
index 11fd3adf..55e6fe4b 100644
--- a/lib/ParameterValidator.php
+++ b/lib/ParameterValidator.php
@@ -195,16 +195,14 @@ class ParameterValidator {
foreach($set as $id => $properties) {
if(isset($data[$id]) && !empty($data[$id])) {
$queriedContexts[$context] = true;
- } elseif(isset($properties['required'])
- && $properties['required'] === true
- && isset($properties['type'])
- && $properties['type'] !== 'checkbox'
- && $properties['type'] !== 'list') {
+ } elseif (isset($properties['type'])
+ && ($properties['type'] === 'checkbox' || $properties['type'] === 'list')) {
+ continue;
+ } elseif(isset($properties['required']) && $properties['required'] === true) {
$queriedContexts[$context] = false;
break;
}
}
-
}
// Abort if one of the globally required parameters is not satisfied
From 24cdeabed814903ccd86b4ed99ebaf534ac3887a Mon Sep 17 00:00:00 2001
From: Lyra
Date: Fri, 5 Apr 2019 10:53:28 +0200
Subject: [PATCH 58/66] [GithubSearchBridge] Update the bridge to match
Github's layout
---
bridges/GithubSearchBridge.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/bridges/GithubSearchBridge.php b/bridges/GithubSearchBridge.php
index fe8a721a..54385598 100644
--- a/bridges/GithubSearchBridge.php
+++ b/bridges/GithubSearchBridge.php
@@ -20,11 +20,11 @@ class GithubSearchBridge extends BridgeAbstract {
'o' => 'desc',
'type' => 'Repositories');
$url = self::URI . 'search?' . http_build_query($params);
-
+
$html = getSimpleHTMLDOM($url)
or returnServerError('Error while downloading the website content');
- foreach($html->find('div.repo-list-item') as $element) {
+ foreach($html->find('li.repo-list-item') as $element) {
$item = array();
$uri = $element->find('h3 a', 0)->href;
From 92775abe11feddcb9ec0591bc914becc0151ade8 Mon Sep 17 00:00:00 2001
From: Lyra
Date: Fri, 5 Apr 2019 10:59:30 +0200
Subject: [PATCH 59/66] Fix phpcs
---
bridges/GithubSearchBridge.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bridges/GithubSearchBridge.php b/bridges/GithubSearchBridge.php
index 54385598..fd90934c 100644
--- a/bridges/GithubSearchBridge.php
+++ b/bridges/GithubSearchBridge.php
@@ -20,7 +20,7 @@ class GithubSearchBridge extends BridgeAbstract {
'o' => 'desc',
'type' => 'Repositories');
$url = self::URI . 'search?' . http_build_query($params);
-
+
$html = getSimpleHTMLDOM($url)
or returnServerError('Error while downloading the website content');
From 6feda2220e4c4ac2b12ca978e47b0f9cebab53ae Mon Sep 17 00:00:00 2001
From: Eugene Molotov
Date: Mon, 8 Apr 2019 00:50:58 +0500
Subject: [PATCH 60/66] [VkBridge] Add option to hide reposts (#1089)
---
bridges/VkBridge.php | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/bridges/VkBridge.php b/bridges/VkBridge.php
index d4e84d95..8653e7c9 100644
--- a/bridges/VkBridge.php
+++ b/bridges/VkBridge.php
@@ -13,6 +13,10 @@ class VkBridge extends BridgeAbstract
'u' => array(
'name' => 'Group or user name',
'required' => true
+ ),
+ 'hide_reposts' => array(
+ 'name' => 'Hide reposts',
+ 'type' => 'checkbox',
)
)
);
@@ -234,6 +238,9 @@ class VkBridge extends BridgeAbstract
}
if (is_object($post->find('div.copy_quote', 0))) {
+ if ($this->getInput('hide_reposts') === true) {
+ continue;
+ }
$copy_quote = $post->find('div.copy_quote', 0);
if ($copy_post_header = $copy_quote->find('div.copy_post_header', 0)) {
$copy_post_header->outertext = '';
From 90bf90d16778a7bc7be7bd35bc13e9a24b370861 Mon Sep 17 00:00:00 2001
From: sysadminstory
Date: Sun, 7 Apr 2019 21:51:48 +0200
Subject: [PATCH 61/66] [BingSearch] Make the bridge compatible with PHP 5.6
(#1084)
* [BingSearch] Make the bridge compatible with PHP 5.6
The use of isset() with an expression is not possible in PHP 5.6. I
fixed it by replacing isset() with "null !== ".
---
bridges/BingSearchBridge.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bridges/BingSearchBridge.php b/bridges/BingSearchBridge.php
index ccfffdf9..eb8a5fc9 100644
--- a/bridges/BingSearchBridge.php
+++ b/bridges/BingSearchBridge.php
@@ -75,7 +75,7 @@ class BingSearchBridge extends BridgeAbstract
public function getName()
{
if ($this->getInput('category')) {
- if (isset(self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')])) {
+ if (self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')] !== null) {
$category = self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')];
} else {
$category = 'Unknown';
From 98c2530984a2f7ed028ae9b099298e760f79fadb Mon Sep 17 00:00:00 2001
From: Lyra
Date: Sun, 7 Apr 2019 22:02:11 +0200
Subject: [PATCH 62/66] [HDWallpapers] Adapt to some website changes (Fixes
#1088). Add wallpapers to enclosures, and select "HD" as the default
resolution
---
bridges/HDWallpapersBridge.php | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/bridges/HDWallpapersBridge.php b/bridges/HDWallpapersBridge.php
index cea6e344..f1579e02 100644
--- a/bridges/HDWallpapersBridge.php
+++ b/bridges/HDWallpapersBridge.php
@@ -16,13 +16,13 @@ class HDWallpapersBridge extends BridgeAbstract {
),
'r' => array(
'name' => 'resolution',
- 'defaultValue' => '1920x1200',
- 'exampleValue' => '1920x1200, 1680x1050,…'
+ 'defaultValue' => 'HD',
+ 'exampleValue' => 'HD, 1920x1200, 1680x1050,…'
)
));
public function collectData(){
- $category = $this->category;
+ $category = $this->getInput('c');
if(strrpos($category, 'wallpapers') !== strlen($category) - strlen('wallpapers')) {
$category .= '-desktop-wallpapers';
}
@@ -45,13 +45,12 @@ class HDWallpapersBridge extends BridgeAbstract {
$thumbnail = $element->find('img', 0);
$item = array();
- // http://www.hdwallpapers.in/download/yosemite_reflections-1680x1050.jpg
$item['uri'] = self::URI
. '/download'
. str_replace('wallpapers.html', $this->getInput('r') . '.jpg', $element->href);
$item['timestamp'] = time();
- $item['title'] = $element->find('p', 0)->text();
+ $item['title'] = $element->find('em1', 0)->text();
$item['content'] = $item['title']
. '
';
+ $item['enclosures'] = array($item['uri']);
$this->items[] = $item;
$num++;
From 8f5151b222d554f58a314d7261c74b9b1f88567d Mon Sep 17 00:00:00 2001
From: somini
Date: Tue, 16 Apr 2019 08:58:22 +0100
Subject: [PATCH 63/66] [SIMARBridge]: Add new bridge (#1055)
* [SIMARBridge]: Add new bridge
---
bridges/SIMARBridge.php | 63 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
create mode 100644 bridges/SIMARBridge.php
diff --git a/bridges/SIMARBridge.php b/bridges/SIMARBridge.php
new file mode 100644
index 00000000..1e446cf5
--- /dev/null
+++ b/bridges/SIMARBridge.php
@@ -0,0 +1,63 @@
+ array(
+ 'interventions' => array(
+ 'type' => 'checkbox',
+ 'name' => 'Incluir Intervenções?',
+ 'defaultValue' => 'checked',
+ )
+ )
+ );
+
+ public function collectData() {
+ $html = getSimpleHTMLDOM(self::getURI())
+ or returnServerError('Could not load content');
+ $e_home = $html->find('#home', 0)
+ or returnServerError('Invalid site structure');
+
+ foreach($e_home->find('span') as $element) {
+ $item = array();
+
+ $item['title'] = 'Rotura: ' . $element->plaintext;
+ $item['content'] = $element->innertext;
+ $item['uid'] = 'urn:sha1:' . hash('sha1', $item['content']);
+
+ $this->items[] = $item;
+ }
+
+ if ($this->getInput('interventions')) {
+ $e_main1 = $html->find('#menu1', 0)
+ or returnServerError('Invalid site structure');
+
+ foreach ($e_main1->find('a') as $element) {
+ $item = array();
+
+ $item['title'] = 'Intervenção: ' . $element->plaintext;
+ $item['uri'] = self::getURI() . $element->href;
+ $item['content'] = $element->innertext;
+
+ /* Try to get the actual contents for this kind of item */
+ $item_html = getSimpleHTMLDOMCached($item['uri']);
+ if ($item_html) {
+ $e_item = $item_html->find('.auto-style59', 0);
+ foreach($e_item->find('p') as $paragraph) {
+ /* Remove empty paragraphs */
+ if (preg_match('/^(\W| )+$/', $paragraph->innertext) == 1) {
+ $paragraph->outertext = '';
+ }
+ }
+ if ($e_item) {
+ $item['content'] = $e_item->innertext;
+ }
+ }
+
+ $this->items[] = $item;
+ }
+ }
+ }
+}
From 7b8dd93a8e5148d5bdacc6c996ef83a4ee99c535 Mon Sep 17 00:00:00 2001
From: Lorenzo Stanco
Date: Sat, 20 Apr 2019 22:15:30 +0200
Subject: [PATCH 64/66] [InstagramBridge] Fix image link
---
bridges/InstagramBridge.php | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/bridges/InstagramBridge.php b/bridges/InstagramBridge.php
index 317fb12e..1bf86072 100644
--- a/bridges/InstagramBridge.php
+++ b/bridges/InstagramBridge.php
@@ -89,7 +89,7 @@ class InstagramBridge extends BridgeAbstract {
if (isset($media->edge_media_to_caption->edges[0]->node->text)) {
$textContent = $media->edge_media_to_caption->edges[0]->node->text;
} else {
- $textContent = basename($media->display_url);
+ $textContent = '(no text)';
}
$item['title'] = ($media->is_video ? '▶ ' : '') . trim($textContent);
@@ -103,10 +103,11 @@ class InstagramBridge extends BridgeAbstract {
$item['content'] = $data[0];
$item['enclosures'] = $data[1];
} else {
+ $mediaURI = self::URI . 'p/' . $media->shortcode . '/media?size=l';
$item['content'] = '';
- $item['content'] .= '';
+ $item['content'] .= '';
$item['content'] .= '
' . nl2br(htmlentities($textContent));
- $item['enclosures'] = array($media->display_url);
+ $item['enclosures'] = array($mediaURI);
}
$item['timestamp'] = $media->taken_at_timestamp;
From f9c4a84c2579bb3687c0e7e9bb1932db10ecefa6 Mon Sep 17 00:00:00 2001
From: sysadminstory
Date: Sat, 20 Apr 2019 22:19:22 +0200
Subject: [PATCH 65/66] [RadioMelodieBridge] Update to support new Website
(#1101)
* [RadioMelodieBridge] Update to support new Website
---
bridges/RadioMelodieBridge.php | 88 +++++++++++++++++++++++++++-------
1 file changed, 71 insertions(+), 17 deletions(-)
diff --git a/bridges/RadioMelodieBridge.php b/bridges/RadioMelodieBridge.php
index ca033fd9..03a14a43 100644
--- a/bridges/RadioMelodieBridge.php
+++ b/bridges/RadioMelodieBridge.php
@@ -1,34 +1,88 @@
find('div[class=actuitem]');
+ $list = $html->find('div[class=actu_col1]', 0)->children();;
foreach($list as $element) {
- $item = array();
+ if($element->tag == 'a') {
+ $articleURL = self::URI . $element->href;
+ $article = getSimpleHTMLDOM($articleURL);
- // Get picture URL
- $pictureHTML = $element->find('div[class=picture]');
- preg_match(
- '/background-image:url\((.*)\);/',
- $pictureHTML[0]->getAttribute('style'),
- $pictures);
- $pictureURL = $pictures[1];
+ // Initialise arrays
+ $item = array();
+ $audio = array();
+ $picture = array();
- $item['enclosures'] = array($pictureURL);
- $item['uri'] = self::URI . $element->parent()->href;
- $item['title'] = $element->find('h3', 0)->plaintext;
- $item['content'] = $element->find('p', 0)->plaintext . '
';
- $this->items[] = $item;
+ // Get the Main picture URL
+ $picture[] = $this->rewriteImage($article->find('img[id=picturearticle]', 0)->src);
+ $audioHTML = $article->find('div[class=sm2-playlist-wrapper]');
+
+ // Remove the audio placeholder under the Audio player with an