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
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
diff --git a/README.md b/README.md
index 4e2c7b1c..7ed74d81 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)
@@ -111,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)
@@ -156,21 +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)
@@ -180,28 +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)
+ * [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
===
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..b223b757
--- /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(Configuration::getConfig('cache', 'type'));
+ $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/bridges/AllocineFRBridge.php b/bridges/AllocineFRBridge.php
index 50a41ec7..17da9031 100644
--- a/bridges/AllocineFRBridge.php
+++ b/bridges/AllocineFRBridge.php
@@ -10,7 +10,6 @@ class AllocineFRBridge extends BridgeAbstract {
'category' => array(
'name' => 'category',
'type' => 'list',
- 'required' => true,
'exampleValue' => 'Faux Raccord',
'title' => 'Select your category',
'values' => array(
diff --git a/bridges/AmazonBridge.php b/bridges/AmazonBridge.php
index c9d4dc9c..bcd83dcc 100644
--- a/bridges/AmazonBridge.php
+++ b/bridges/AmazonBridge.php
@@ -16,7 +16,6 @@ class AmazonBridge extends BridgeAbstract {
'sort' => array(
'name' => 'Sort by',
'type' => 'list',
- 'required' => false,
'values' => array(
'Relevance' => 'relevanceblender',
'Price: Low to High' => 'price-asc-rank',
@@ -29,7 +28,6 @@ class AmazonBridge extends BridgeAbstract {
'tld' => array(
'name' => 'Country',
'type' => 'list',
- 'required' => true,
'values' => array(
'Australia' => 'com.au',
'Brazil' => 'com.br',
diff --git a/bridges/AmazonPriceTrackerBridge.php b/bridges/AmazonPriceTrackerBridge.php
index e31a03bb..6fa11c91 100644
--- a/bridges/AmazonPriceTrackerBridge.php
+++ b/bridges/AmazonPriceTrackerBridge.php
@@ -19,7 +19,6 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
'tld' => array(
'name' => 'Country',
'type' => 'list',
- 'required' => true,
'values' => array(
'Australia' => 'com.au',
'Brazil' => 'com.br',
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'];
+ });
+ }
+}
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;
+ }
+ }
+}
diff --git a/bridges/AutoJMBridge.php b/bridges/AutoJMBridge.php
index 598f0431..e8490d95 100644
--- a/bridges/AutoJMBridge.php
+++ b/bridges/AutoJMBridge.php
@@ -3,63 +3,195 @@
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'))
- 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';
- }
- $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;
+ public function getName() {
+ switch($this->queriedContext) {
+ case 'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM':
+ $html = getSimpleHTMLDOMCached(self::URI . $this->getInput('url'), 86400);
+ $name = html_entity_decode($html->find('title', 0)->plaintext);
+ return $name;
+ break;
+ default:
+ return parent::getName();
}
}
+
+ public function collectData() {
+
+ $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.');
+
+ // 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;
+ }
+ }
+ }
+
+ /**
+ * 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;
+ }
}
diff --git a/bridges/BakaUpdatesMangaReleasesBridge.php b/bridges/BakaUpdatesMangaReleasesBridge.php
index e6da64b6..cde9be84 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 = $this->filterHTML($rows[0][1]->plaintext);
+ }
+
+ foreach($rows as $cols) {
+ if (count($cols) < self::LIMIT_COLS) continue;
$item = array();
$title = array();
@@ -47,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];
@@ -61,18 +62,15 @@ 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);
- $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;
- }
}
}
@@ -90,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));
+ }
}
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(){
diff --git a/bridges/BingSearchBridge.php b/bridges/BingSearchBridge.php
new file mode 100644
index 00000000..eb8a5fc9
--- /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 (self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')] !== null) {
+ $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) . '...';
+ }
+}
diff --git a/bridges/BundesbankBridge.php b/bridges/BundesbankBridge.php
index d21f22b6..b64a6425 100644
--- a/bridges/BundesbankBridge.php
+++ b/bridges/BundesbankBridge.php
@@ -17,7 +17,6 @@ class BundesbankBridge extends BridgeAbstract {
self::PARAM_LANG => array(
'name' => 'Language',
'type' => 'list',
- 'required' => true,
'defaultValue' => self::LANG_DE,
'values' => array(
'English' => self::LANG_EN,
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;
+ }
+ }
+}
diff --git a/bridges/ComboiosDePortugalBridge.php b/bridges/ComboiosDePortugalBridge.php
new file mode 100644
index 00000000..610e23b3
--- /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 . implode('/', array_map('urlencode', explode('/', $element->href)));
+
+ $this->items[] = $item;
+ }
+ }
+}
diff --git a/bridges/ContainerLinuxReleasesBridge.php b/bridges/ContainerLinuxReleasesBridge.php
index ae438885..d2f63256 100644
--- a/bridges/ContainerLinuxReleasesBridge.php
+++ b/bridges/ContainerLinuxReleasesBridge.php
@@ -15,7 +15,6 @@ class ContainerLinuxReleasesBridge extends BridgeAbstract {
'channel' => [
'name' => 'Release Channel',
'type' => 'list',
- 'required' => true,
'defaultValue' => self::STABLE,
'values' => [
'Stable' => self::STABLE,
diff --git a/bridges/DealabsBridge.php b/bridges/DealabsBridge.php
index 89183edb..b64bb1dc 100644
--- a/bridges/DealabsBridge.php
+++ b/bridges/DealabsBridge.php
@@ -15,13 +15,11 @@ class DealabsBridge extends PepperBridgeAbstract {
'hide_expired' => array(
'name' => 'Masquer les éléments expirés',
'type' => 'checkbox',
- 'required' => true
),
'hide_local' => array(
'name' => 'Masquer les deals locaux',
'type' => 'checkbox',
'title' => 'Masquer les deals en magasins physiques',
- 'required' => true
),
'priceFrom' => array(
'name' => 'Prix minimum',
@@ -41,7 +39,6 @@ class DealabsBridge extends PepperBridgeAbstract {
'group' => array(
'name' => 'Groupe',
'type' => 'list',
- 'required' => true,
'title' => 'Groupe dont il faut afficher les deals',
'values' => array(
'Abonnements internet' => 'abonnements-internet',
@@ -957,7 +954,6 @@ class DealabsBridge extends PepperBridgeAbstract {
'order' => array(
'name' => 'Trier par',
'type' => 'list',
- 'required' => true,
'title' => 'Ordre de tri des deals',
'values' => array(
'Du deal le plus Hot au moins Hot' => '',
@@ -1380,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();
}
diff --git a/bridges/DesoutterBridge.php b/bridges/DesoutterBridge.php
index 14e26c26..4a7b0a94 100644
--- a/bridges/DesoutterBridge.php
+++ b/bridges/DesoutterBridge.php
@@ -15,7 +15,6 @@ class DesoutterBridge extends BridgeAbstract {
'news_lang' => array(
'name' => 'Language',
'type' => 'list',
- 'required' => true,
'title' => 'Select your language',
'defaultValue' => 'Corporate',
'values' => array(
@@ -66,7 +65,6 @@ class DesoutterBridge extends BridgeAbstract {
'industry_lang' => array(
'name' => 'Language',
'type' => 'list',
- 'required' => true,
'title' => 'Select your language',
'defaultValue' => 'Corporate',
'values' => array(
@@ -117,7 +115,6 @@ class DesoutterBridge extends BridgeAbstract {
'full' => array(
'name' => 'Load full articles',
'type' => 'checkbox',
- 'required' => false,
'title' => 'Enable to load the full article for each item'
)
)
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;
+ }
+ }
+}
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/ExtremeDownloadBridge.php b/bridges/ExtremeDownloadBridge.php
index 52729971..acdf6301 100644
--- a/bridges/ExtremeDownloadBridge.php
+++ b/bridges/ExtremeDownloadBridge.php
@@ -15,7 +15,6 @@ class ExtremeDownloadBridge extends BridgeAbstract {
'filter' => array(
'name' => 'Type de contenu',
'type' => 'list',
- 'required' => true,
'title' => 'Type de contenu à suivre : Téléchargement, Streaming ou les deux',
'values' => array(
'Streaming et Téléchargement' => 'both',
diff --git a/bridges/FDroidBridge.php b/bridges/FDroidBridge.php
index b606cec1..7f54735a 100644
--- a/bridges/FDroidBridge.php
+++ b/bridges/FDroidBridge.php
@@ -11,7 +11,6 @@ class FDroidBridge extends BridgeAbstract {
'u' => array(
'name' => 'Widget selection',
'type' => 'list',
- 'required' => true,
'values' => array(
'Latest added apps' => 'added',
'Latest updated apps' => 'updated'
@@ -29,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;
}
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) {
diff --git a/bridges/FeedExpanderExampleBridge.php b/bridges/FeedExpanderExampleBridge.php
index 537a6352..f5b83071 100644
--- a/bridges/FeedExpanderExampleBridge.php
+++ b/bridges/FeedExpanderExampleBridge.php
@@ -11,7 +11,6 @@ class FeedExpanderExampleBridge extends FeedExpander {
'version' => array(
'name' => 'Version',
'type' => 'list',
- 'required' => true,
'title' => 'Select your feed format/version',
'defaultValue' => 'RSS 2.0',
'values' => array(
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);
diff --git a/bridges/GBAtempBridge.php b/bridges/GBAtempBridge.php
index 9383be76..48a7f851 100644
--- a/bridges/GBAtempBridge.php
+++ b/bridges/GBAtempBridge.php
@@ -10,7 +10,6 @@ class GBAtempBridge extends BridgeAbstract {
'type' => array(
'name' => 'Type',
'type' => 'list',
- 'required' => true,
'values' => array(
'News' => 'N',
'Reviews' => 'R',
diff --git a/bridges/GithubSearchBridge.php b/bridges/GithubSearchBridge.php
index fe8a721a..fd90934c 100644
--- a/bridges/GithubSearchBridge.php
+++ b/bridges/GithubSearchBridge.php
@@ -24,7 +24,7 @@ class GithubSearchBridge extends BridgeAbstract {
$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;
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();
+ }
+}
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++;
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;
+ }
+}
diff --git a/bridges/HotUKDealsBridge.php b/bridges/HotUKDealsBridge.php
index 8c1b3bd1..ed2d28af 100644
--- a/bridges/HotUKDealsBridge.php
+++ b/bridges/HotUKDealsBridge.php
@@ -17,13 +17,11 @@ class HotUKDealsBridge extends PepperBridgeAbstract {
'hide_expired' => array(
'name' => 'Hide expired deals',
'type' => 'checkbox',
- 'required' => true
),
'hide_local' => array(
'name' => 'Hide local deals',
'type' => 'checkbox',
'title' => 'Hide deals in physical store',
- 'required' => true
),
'priceFrom' => array(
'name' => 'Minimal Price',
@@ -43,7 +41,6 @@ class HotUKDealsBridge extends PepperBridgeAbstract {
'group' => array(
'name' => 'Group',
'type' => 'list',
- 'required' => true,
'title' => 'Group whose deals must be displayed',
'values' => array(
'2DS' => '2ds',
@@ -1317,7 +1314,6 @@ class HotUKDealsBridge extends PepperBridgeAbstract {
'order' => array(
'name' => 'Order by',
'type' => 'list',
- 'required' => true,
'title' => 'Sort order of deals',
'values' => array(
'From the most to the least hot deal' => '-hot',
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;
diff --git a/bridges/InstructablesBridge.php b/bridges/InstructablesBridge.php
index 05f1a91c..7e7d5428 100644
--- a/bridges/InstructablesBridge.php
+++ b/bridges/InstructablesBridge.php
@@ -21,7 +21,6 @@ class InstructablesBridge extends BridgeAbstract {
'category' => array(
'name' => 'Category',
'type' => 'list',
- 'required' => true,
'values' => array(
'Play' => array(
'All' => '/play/',
@@ -240,7 +239,6 @@ class InstructablesBridge extends BridgeAbstract {
'filter' => array(
'name' => 'Filter',
'type' => 'list',
- 'required' => true,
'values' => array(
'Featured' => ' ',
'Recent' => 'recent/',
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);
+ }
+ }
+}
diff --git a/bridges/JustETFBridge.php b/bridges/JustETFBridge.php
index 85318b8e..8d5b3d5a 100644
--- a/bridges/JustETFBridge.php
+++ b/bridges/JustETFBridge.php
@@ -34,7 +34,6 @@ class JustETFBridge extends BridgeAbstract {
'global' => array(
'lang' => array(
'name' => 'Language',
- 'required' => true,
'type' => 'list',
'values' => array(
'Englisch' => 'en',
diff --git a/bridges/KununuBridge.php b/bridges/KununuBridge.php
index 2f4bf0b2..cb13ca35 100644
--- a/bridges/KununuBridge.php
+++ b/bridges/KununuBridge.php
@@ -11,7 +11,6 @@ class KununuBridge extends BridgeAbstract {
'site' => array(
'name' => 'Site',
'type' => 'list',
- 'required' => true,
'title' => 'Select your site',
'values' => array(
'Austria' => 'at',
@@ -23,7 +22,6 @@ class KununuBridge extends BridgeAbstract {
'full' => array(
'name' => 'Load full article',
'type' => 'checkbox',
- 'required' => false,
'exampleValue' => 'checked',
'title' => 'Activate to load full article'
)
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;
diff --git a/bridges/MangareaderBridge.php b/bridges/MangareaderBridge.php
index 91537069..9ecb0feb 100644
--- a/bridges/MangareaderBridge.php
+++ b/bridges/MangareaderBridge.php
@@ -13,7 +13,6 @@ class MangareaderBridge extends BridgeAbstract {
'category' => array(
'name' => 'Category',
'type' => 'list',
- 'required' => true,
'values' => array(
'All' => 'all',
'Action' => 'action',
diff --git a/bridges/MozillaBugTrackerBridge.php b/bridges/MozillaBugTrackerBridge.php
new file mode 100644
index 00000000..356bedcf
--- /dev/null
+++ b/bridges/MozillaBugTrackerBridge.php
@@ -0,0 +1,153 @@
+ 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;
+ }
+}
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;
}
}
diff --git a/bridges/MydealsBridge.php b/bridges/MydealsBridge.php
index aa5bb489..603f4e07 100644
--- a/bridges/MydealsBridge.php
+++ b/bridges/MydealsBridge.php
@@ -17,13 +17,11 @@ class MydealsBridge extends PepperBridgeAbstract {
'hide_expired' => array(
'name' => 'Abgelaufenes ausblenden',
'type' => 'checkbox',
- 'required' => true
),
'hide_local' => array(
'name' => 'Lokales ausblenden',
'type' => 'checkbox',
'title' => 'Deals im physischen Geschäft ausblenden',
- 'required' => true
),
'priceFrom' => array(
'name' => 'Minimaler Preis',
@@ -43,7 +41,6 @@ class MydealsBridge extends PepperBridgeAbstract {
'group' => array(
'name' => 'Gruppen',
'type' => 'list',
- 'required' => true,
'title' => 'Gruppe, deren Deals angezeigt werden müssen',
'values' => array(
'Elektronik' => 'elektronik',
@@ -66,7 +63,6 @@ class MydealsBridge extends PepperBridgeAbstract {
'order' => array(
'name' => 'sortieren nach',
'type' => 'list',
- 'required' => true,
'title' => 'Sortierung der deals',
'values' => array(
'Vom heißesten zum kältesten Deal' => '',
diff --git a/bridges/NineGagBridge.php b/bridges/NineGagBridge.php
index f5261356..e726c732 100644
--- a/bridges/NineGagBridge.php
+++ b/bridges/NineGagBridge.php
@@ -11,7 +11,6 @@ class NineGagBridge extends BridgeAbstract {
'd' => array(
'name' => 'Section',
'type' => 'list',
- 'required' => true,
'values' => array(
'Hot' => 'hot',
'Trending' => 'trending',
@@ -28,7 +27,6 @@ class NineGagBridge extends BridgeAbstract {
'g' => array(
'name' => 'Section',
'type' => 'list',
- 'required' => true,
'values' => array(
'Animals' => 'cute',
'Anime & Manga' => 'anime-manga',
@@ -88,7 +86,6 @@ class NineGagBridge extends BridgeAbstract {
't' => array(
'name' => 'Type',
'type' => 'list',
- 'required' => true,
'values' => array(
'Hot' => 'hot',
'Fresh' => 'fresh',
diff --git a/bridges/NotAlwaysBridge.php b/bridges/NotAlwaysBridge.php
index b2f4c35a..c7758c32 100644
--- a/bridges/NotAlwaysBridge.php
+++ b/bridges/NotAlwaysBridge.php
@@ -21,8 +21,7 @@ class NotAlwaysBridge extends BridgeAbstract {
'Friendly' => 'friendly',
'Hopeless' => 'hopeless',
'Unfiltered' => 'unfiltered'
- ),
- 'required' => true
+ )
)
));
diff --git a/bridges/OnVaSortirBridge.php b/bridges/OnVaSortirBridge.php
index ee6baf19..ed1dcb65 100644
--- a/bridges/OnVaSortirBridge.php
+++ b/bridges/OnVaSortirBridge.php
@@ -9,7 +9,6 @@ class OnVaSortirBridge extends FeedExpander {
'city' => array(
'name' => 'City',
'type' => 'list',
- 'required' => true,
'values' => array(
'Agen' => 'Agen',
'Ajaccio' => 'Ajaccio',
diff --git a/bridges/OneFortuneADayBridge.php b/bridges/OneFortuneADayBridge.php
index ed0b5ec6..62fe767d 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 = gmdate('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;
diff --git a/bridges/OpenClassroomsBridge.php b/bridges/OpenClassroomsBridge.php
index 5f0daca4..4db7bc19 100644
--- a/bridges/OpenClassroomsBridge.php
+++ b/bridges/OpenClassroomsBridge.php
@@ -11,7 +11,6 @@ class OpenClassroomsBridge extends BridgeAbstract {
'u' => array(
'name' => 'Catégorie',
'type' => 'list',
- 'required' => true,
'values' => array(
'Arts & Culture' => 'arts',
'Code' => 'code',
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