diff --git a/.dockerignore b/.dockerignore
index f2bc0e8d..db313697 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,7 +1,14 @@
.git
+.gitattributes
+.github/*
+.travis.yml
cache/*
+CONTRIBUTING.md
DEBUG
Dockerfile
-whitelist.txt
+phpcompatibility.xml
phpcs.xml
-CONTRIBUTING.md
\ No newline at end of file
+phpcs.xml
+scalingo.json
+tests/*
+whitelist.txt
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
index a141bb12..13ebe2ca 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -10,27 +10,40 @@
*.dbproj merge=union
# Standard to msysgit
-*.doc diff=astextplain
-*.DOC diff=astextplain
-*.docx diff=astextplain
-*.DOCX diff=astextplain
-*.dot diff=astextplain
-*.DOT diff=astextplain
-*.pdf diff=astextplain
-*.PDF diff=astextplain
-*.rtf diff=astextplain
-*.RTF diff=astextplain
+*.doc diff=astextplain
+*.DOC diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot diff=astextplain
+*.DOT diff=astextplain
+*.pdf diff=astextplain
+*.PDF diff=astextplain
+*.rtf diff=astextplain
+*.RTF diff=astextplain
# Ignore files in git archive (i.e. GitHub release builds)
-Dockerfile export-ignore
-.travis.yml export-ignore
-.github/ export-ignore
-.gitattributes export-ignore
-.gitignore export-ignore
-.dockerignore export-ignore
-scalingo.json export-ignore
-phpunit.xml export-ignore
-phpcs.xml export-ignore
-phpcompatibility.xml export-ignore
-tests/ export-ignore
-cache/.gitkeep export-ignore
\ No newline at end of file
+## Docker
+Dockerfile export-ignore
+.dockerignore export-ignore
+## Travis
+.travis.yml export-ignore
+## GitHub
+.github/ export-ignore
+## Git
+.gitattributes export-ignore
+.gitignore export-ignore
+## Scalingo
+scalingo.json export-ignore
+## RSS-Bridge
+phpunit.xml export-ignore
+phpcs.xml export-ignore
+phpcompatibility.xml export-ignore
+tests/ export-ignore
+cache/.gitkeep export-ignore
+bridges/DemoBridge.php export-ignore
+bridges/FeedExpanderExampleBridge.php export-ignore
+## Composer
+composer.json export-ignore
+composer.lock export-ignore
+## Heroku
+app.json export-ignore
diff --git a/.github/ISSUE_TEMPLATE/bridge-request-template.md b/.github/ISSUE_TEMPLATE/bridge-request.md
similarity index 96%
rename from .github/ISSUE_TEMPLATE/bridge-request-template.md
rename to .github/ISSUE_TEMPLATE/bridge-request.md
index f4b1119f..a0080b8b 100644
--- a/.github/ISSUE_TEMPLATE/bridge-request-template.md
+++ b/.github/ISSUE_TEMPLATE/bridge-request.md
@@ -1,6 +1,9 @@
---
-name: Bridge request template
+name: Bridge request
about: Use this template for requesting a new bridge
+title: Bridge request for ...
+labels: Bridge-Request
+assignees: ''
---
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..70d49ffb
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,38 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: Bug-Report
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+ - Version [e.g. 22]
+
+**Smartphone (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Browser [e.g. stock browser, safari]
+ - Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..8a5d06a9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: Feature-Request
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.gitignore b/.gitignore
index d970fed9..680260c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -240,3 +240,6 @@ config.ini.php
#Auth
.htaccess
.htpasswd
+
+#Crawler
+robots.txt
diff --git a/Dockerfile b/Dockerfile
index 35caac84..7d0611be 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,11 @@
-FROM ulsmith/alpine-apache-php7
+FROM php:7-apache
-COPY ./ /app/public/
+ENV APACHE_DOCUMENT_ROOT=/app
-RUN chown -R apache:root /app/public
\ No newline at end of file
+RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
+ && apt-get --yes update && apt-get --yes install libxml2-dev \
+ && docker-php-ext-install -j$(nproc) simplexml \
+ && sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
+ && sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
+
+COPY --chown=www-data:www-data ./ /app/
\ No newline at end of file
diff --git a/README.md b/README.md
index 530f90fe..95086c03 100644
--- a/README.md
+++ b/README.md
@@ -85,7 +85,7 @@ Deploy
Thanks to the community, hosting your own instance of RSS-Bridge is as easy as clicking a button!
[![Deploy on Scalingo](https://cdn.scalingo.com/deploy/button.svg)](https://my.scalingo.com/deploy?source=https://github.com/sebsauvage/rss-bridge)
-[![Deploy to Docker Cloud](https://files.cloud.docker.com/images/deploy-to-dockercloud.svg)](https://cloud.docker.com/stack/deploy/?repo=https://github.com/rss-bridge/rss-bridge)
+[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
Getting involved
===
@@ -116,13 +116,14 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [Ahiles3005](https://github.com/Ahiles3005)
* [Albirew](https://github.com/Albirew)
* [aledeg](https://github.com/aledeg)
+ * [alex73](https://github.com/alex73)
* [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)
* [az5he6ch](https://github.com/az5he6ch)
+ * [azdkj532](https://github.com/azdkj532)
* [b1nj](https://github.com/b1nj)
* [benasse](https://github.com/benasse)
* [captn3m0](https://github.com/captn3m0)
@@ -156,6 +157,7 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [jdigilio](https://github.com/jdigilio)
* [JeremyRand](https://github.com/JeremyRand)
* [Jocker666z](https://github.com/Jocker666z)
+ * [killruana](https://github.com/killruana)
* [klimplant](https://github.com/klimplant)
* [kranack](https://github.com/kranack)
* [kraoc](https://github.com/kraoc)
@@ -173,7 +175,6 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [mdemoss](https://github.com/mdemoss)
* [melangue](https://github.com/melangue)
* [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)
@@ -208,8 +209,10 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [thefranke](https://github.com/thefranke)
* [TheRadialActive](https://github.com/TheRadialActive)
* [triatic](https://github.com/triatic)
+ * [VerifiedJoseph](https://github.com/VerifiedJoseph)
* [WalterBarrett](https://github.com/WalterBarrett)
* [wtuuju](https://github.com/wtuuju)
+ * [xurxof](https://github.com/xurxof)
* [yardenac](https://github.com/yardenac)
* [ZeNairolf](https://github.com/ZeNairolf)
diff --git a/app.json b/app.json
new file mode 100644
index 00000000..f1847995
--- /dev/null
+++ b/app.json
@@ -0,0 +1,8 @@
+{
+ "service": "Heroku",
+ "name": "RSS-Bridge",
+ "description": "RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites which don't have one.",
+ "repository": "https://github.com/RSS-Bridge/rss-bridge",
+ "keywords": ["php", "rss-bridge", "rss"]
+}
+
diff --git a/bridges/Arte7Bridge.php b/bridges/Arte7Bridge.php
index ff722113..562f648f 100644
--- a/bridges/Arte7Bridge.php
+++ b/bridges/Arte7Bridge.php
@@ -91,7 +91,8 @@ class Arte7Bridge extends BridgeAbstract {
'Authorization: Bearer ' . self::API_TOKEN
);
- $input = getContents($url, $header) or die('Could not request ARTE.');
+ $input = getContents($url, $header)
+ or returnServerError('Could not request ARTE.');
$input_json = json_decode($input, true);
foreach($input_json['videos'] as $element) {
diff --git a/bridges/BinanceBridge.php b/bridges/BinanceBridge.php
new file mode 100644
index 00000000..9653ab73
--- /dev/null
+++ b/bridges/BinanceBridge.php
@@ -0,0 +1,103 @@
+ array(
+ 'name' => 'category',
+ 'type' => 'list',
+ 'exampleValue' => 'Blog',
+ 'title' => 'Select a category',
+ 'values' => array(
+ 'Blog' => 'Blog',
+ 'Announcements' => 'Announcements'
+ )
+ )
+ ));
+
+ public function getIcon() {
+ return 'https://bin.bnbstatic.com/static/images/common/favicon.ico';
+ }
+
+ public function getName() {
+ return self::NAME . ' ' . $this->getInput('category');
+ }
+
+ public function getURI() {
+ if ($this->getInput('category') == 'Blog')
+ return self::URI . '/en/blog';
+ else
+ return 'https://binance.zendesk.com/hc/en-us/categories/115000056351-Announcements';
+ }
+
+ protected function collectBlogData() {
+ $html = getSimpleHTMLDOM($this->getURI())
+ or returnServerError('Could not fetch Binance blog data.');
+
+ foreach($html->find('div[direction="row"]') as $element) {
+
+ $date = $element->find('div[direction="column"]', 0);
+ $day = $date->find('div', 0)->innertext;
+ $month = $date->find('div', 1)->innertext;
+ $extractedDate = $day . ' ' . $month;
+
+ $abstract = $element->find('div[direction="column"]', 1);
+ $a = $abstract->find('a', 0);
+ $uri = self::URI . $a->href;
+ $title = $a->innertext;
+
+ $full = getSimpleHTMLDOMCached($uri);
+ $content = $full->find('div.desc', 1);
+
+ $item = array();
+ $item['title'] = $title;
+ $item['uri'] = $uri;
+ $item['timestamp'] = strtotime($extractedDate);
+ $item['author'] = 'Binance';
+ $item['content'] = $content;
+
+ $this->items[] = $item;
+
+ if (count($this->items) >= 10)
+ break;
+ }
+ }
+
+ protected function collectAnnouncementData() {
+ $html = getSimpleHTMLDOM($this->getURI())
+ or returnServerError('Could not fetch Zendesk announcement data.');
+
+ foreach($html->find('a.article-list-link') as $a) {
+ $title = $a->innertext;
+ $uri = 'https://binance.zendesk.com' . $a->href;
+
+ $full = getSimpleHTMLDOMCached($uri);
+ $content = $full->find('div.article-body', 0);
+ $date = $full->find('time', 0)->getAttribute('datetime');
+
+ $item = array();
+
+ $item['title'] = $title;
+ $item['uri'] = $uri;
+ $item['timestamp'] = strtotime($date);
+ $item['author'] = 'Binance';
+ $item['content'] = $content;
+
+ $this->items[] = $item;
+
+ if (count($this->items) >= 10)
+ break;
+ }
+ }
+
+ public function collectData() {
+ if ($this->getInput('category') == 'Blog')
+ $this->collectBlogData();
+ else
+ $this->collectAnnouncementData();
+ }
+}
diff --git a/bridges/BrutBridge.php b/bridges/BrutBridge.php
new file mode 100644
index 00000000..432cb502
--- /dev/null
+++ b/bridges/BrutBridge.php
@@ -0,0 +1,142 @@
+ array(
+ 'name' => 'Category',
+ 'type' => 'list',
+ 'values' => array(
+ 'News' => 'news',
+ 'International' => 'international',
+ 'Economy' => 'economy',
+ 'Science and Technology' => 'science-and-technology',
+ 'Entertainment' => 'entertainment',
+ 'Sports' => 'sport',
+ 'Nature' => 'nature',
+ ),
+ 'defaultValue' => 'news',
+ ),
+ 'edition' => array(
+ 'name' => ' Edition',
+ 'type' => 'list',
+ 'values' => array(
+ 'United States' => 'us',
+ 'United Kingdom' => 'uk',
+ 'France' => 'fr',
+ 'India' => 'in',
+ 'Mexico' => 'mx',
+ ),
+ 'defaultValue' => 'us',
+ )
+ )
+ );
+
+ const CACHE_TIMEOUT = 1800; // 30 mins
+
+ private $videoId = '';
+ private $videoType = '';
+ private $videoImage = '';
+
+ public function collectData() {
+
+ $html = getSimpleHTMLDOM($this->getURI())
+ or returnServerError('Could not request: ' . $this->getURI());
+
+ $results = $html->find('div.results', 0);
+
+ foreach($results->find('li.col-6.col-sm-4.col-md-3.col-lg-2.px-2.pb-4') as $index => $li) {
+ $item = array();
+
+ $videoPath = self::URI . $li->children(0)->href;
+
+ $videoPageHtml = getSimpleHTMLDOMCached($videoPath, 3600)
+ or returnServerError('Could not request: ' . $videoPath);
+
+ $this->videoImage = $videoPageHtml->find('meta[name="twitter:image"]', 0)->content;
+
+ $this->processTwitterImage();
+
+ $description = $videoPageHtml->find('div.description', 0);
+
+ $item['uri'] = $videoPath;
+ $item['title'] = $description->find('h1', 0)->plaintext;
+
+ if ($description->find('div.date', 0)->children(0)) {
+ $description->find('div.date', 0)->children(0)->outertext = '';
+ }
+
+ $item['content'] = $this->processContent(
+ $description
+ );
+
+ $item['timestamp'] = $this->processDate($description);
+ $item['enclosures'][] = $this->videoImage;
+
+ $this->items[] = $item;
+
+ if (count($this->items) >= 5) {
+ break;
+ }
+ }
+ }
+
+ public function getURI() {
+
+ if (!is_null($this->getInput('edition')) && !is_null($this->getInput('category'))) {
+ return self::URI . '/' . $this->getInput('edition') . '/' . $this->getInput('category');
+ }
+
+ return parent::getURI();
+ }
+
+ private function processDate($description) {
+
+ if ($this->getInput('edition') === 'uk') {
+ $date = DateTime::createFromFormat('d/m/Y H:i', $description->find('div.date', 0)->innertext);
+ return strtotime($date->format('Y-m-d H:i:s'));
+ }
+
+ return strtotime($description->find('div.date', 0)->innertext);
+ }
+
+ private function processContent($description) {
+
+ $content = '';
+ $content .= '
' . $description->find('h2.mb-1', 0)->innertext . '
';
+
+ if ($description->find('div.text.pb-3', 0)->children(1)->class != 'date') {
+ $content .= '' . $description->find('div.text.pb-3', 0)->children(1)->innertext . '
';
+ }
+
+ return $content;
+ }
+
+ private function processTwitterImage() {
+ /**
+ * Extract video ID + type from twitter image
+ *
+ * Example (wrapped):
+ * https://img.brut.media/thumbnail/
+ * the-life-of-rita-moreno-2cce75b5-d448-44d2-a97c-ca50d6470dd4-square.jpg
+ * ?ts=1559337892
+ */
+ $fpath = parse_url($this->videoImage, PHP_URL_PATH);
+ $fname = basename($fpath);
+ $fname = substr($fname, 0, strrpos($fname, '.'));
+ $parts = explode('-', $fname);
+
+ if (end($parts) === 'auto') {
+ $key = array_search('auto', $parts);
+ unset($parts[$key]);
+ }
+
+ $this->videoId = implode('-', array_splice($parts, -6, 5));
+ $this->videoType = end($parts);
+ }
+}
diff --git a/bridges/FB2Bridge.php b/bridges/FB2Bridge.php
index 29df7554..2faa3215 100644
--- a/bridges/FB2Bridge.php
+++ b/bridges/FB2Bridge.php
@@ -72,15 +72,15 @@ class FB2Bridge extends BridgeAbstract {
$pageInfo = $this->getPageInfos($page, $cookies);
if($pageInfo['userId'] === null) {
- echo <<find('article') as $content) {
$item = array();
- //echo $content; die();
+
preg_match('/publish_time\\\":([0-9]+),/', $content->getAttribute('data-store', 0), $match);
if(isset($match[1]))
$timestamp = $match[1];
diff --git a/bridges/GOGBridge.php b/bridges/GOGBridge.php
index 669332f0..09f47b4b 100644
--- a/bridges/GOGBridge.php
+++ b/bridges/GOGBridge.php
@@ -8,8 +8,8 @@ class GOGBridge extends BridgeAbstract {
public function collectData() {
- $values = getContents('https://www.gog.com/games/ajax/filtered?limit=25&sort=new') or
- die('Unable to get the news pages from GOG !');
+ $values = getContents('https://www.gog.com/games/ajax/filtered?limit=25&sort=new')
+ or returnServerError('Unable to get the news pages from GOG !');
$decodedValues = json_decode($values);
$limit = 0;
@@ -38,8 +38,8 @@ class GOGBridge extends BridgeAbstract {
private function buildGameContentPage($game) {
- $gameDescriptionText = getContents('https://api.gog.com/products/' . $game->id . '?expand=description') or
- die('Unable to get game description from GOG !');
+ $gameDescriptionText = getContents('https://api.gog.com/products/' . $game->id . '?expand=description')
+ or returnServerError('Unable to get game description from GOG !');
$gameDescriptionValue = json_decode($gameDescriptionText);
diff --git a/bridges/HaveIBeenPwnedBridge.php b/bridges/HaveIBeenPwnedBridge.php
new file mode 100644
index 00000000..f256623a
--- /dev/null
+++ b/bridges/HaveIBeenPwnedBridge.php
@@ -0,0 +1,102 @@
+ array(
+ 'name' => 'Order by',
+ 'type' => 'list',
+ 'values' => array(
+ 'Breach date' => 'breachDate',
+ 'Date added to HIBP' => 'dateAdded',
+ ),
+ 'defaultValue' => 'dateAdded',
+ )
+ ));
+
+ const CACHE_TIMEOUT = 3600;
+
+ private $breachDateRegex = '/Breach date: ([0-9]{1,2} [A-Z-a-z]+ [0-9]{4})/';
+ private $dateAddedRegex = '/Date added to HIBP: ([0-9]{1,2} [A-Z-a-z]+ [0-9]{4})/';
+ private $accountsRegex = '/Compromised accounts: ([0-9,]+)/';
+
+ private $breaches = array();
+
+ public function collectData() {
+
+ $html = getSimpleHTMLDOM(self::URI . '/PwnedWebsites')
+ or returnServerError('Could not request: ' . self::URI . '/PwnedWebsites');
+
+ $breaches = array();
+
+ foreach($html->find('div.row') as $breach) {
+ $item = array();
+
+ if ($breach->class != 'row') {
+ continue;
+ }
+
+ preg_match($this->breachDateRegex, $breach->find('p', 1)->plaintext, $breachDate)
+ or returnServerError('Could not extract details');
+
+ preg_match($this->dateAddedRegex, $breach->find('p', 1)->plaintext, $dateAdded)
+ or returnServerError('Could not extract details');
+
+ preg_match($this->accountsRegex, $breach->find('p', 1)->plaintext, $accounts)
+ or returnServerError('Could not extract details');
+
+ $permalink = $breach->find('p', 1)->find('a', 0)->href;
+
+ // Remove permalink
+ $breach->find('p', 1)->find('a', 0)->outertext = '';
+
+ $item['title'] = $breach->find('h3', 0)->plaintext . ' - ' . $accounts[1] . ' breached accounts';
+ $item['dateAdded'] = strtotime($dateAdded[1]);
+ $item['breachDate'] = strtotime($breachDate[1]);
+ $item['uri'] = self::URI . '/PwnedWebsites' . $permalink;
+
+ $item['content'] = '' . $breach->find('p', 0)->innertext . '
';
+ $item['content'] .= '
' . $breach->find('p', 1)->innertext . '
';
+
+ $this->breaches[] = $item;
+ }
+
+ $this->orderBreaches();
+ $this->createItems();
+ }
+
+ /**
+ * Order Breaches by date added or date breached
+ */
+ private function orderBreaches() {
+
+ $sortBy = $this->getInput('order');
+ $sort = array();
+
+ foreach ($this->breaches as $key => $item) {
+ $sort[$key] = $item[$sortBy];
+ }
+
+ array_multisort($sort, SORT_DESC, $this->breaches);
+
+ }
+
+ /**
+ * Create items from breaches array
+ */
+ private function createItems() {
+
+ foreach ($this->breaches as $breach) {
+ $item = array();
+
+ $item['title'] = $breach['title'];
+ $item['timestamp'] = $breach[$this->getInput('order')];
+ $item['uri'] = $breach['uri'];
+ $item['content'] = $breach['content'];
+
+ $this->items[] = $item;
+ }
+ }
+}
diff --git a/bridges/MediapartBridge.php b/bridges/MediapartBridge.php
new file mode 100644
index 00000000..15d1d3ea
--- /dev/null
+++ b/bridges/MediapartBridge.php
@@ -0,0 +1,60 @@
+ array(
+ 'name' => 'Single page article',
+ 'type' => 'checkbox',
+ 'title' => 'Display long articles on a single page',
+ 'defaultValue' => 'checked'
+ ),
+ 'mpsessid' => array(
+ 'name' => 'MPSESSID',
+ 'type' => 'text',
+ 'title' => 'Value of the session cookie MPSESSID'
+ )
+ )
+ );
+ const CACHE_TIMEOUT = 7200; // 2h
+ const DESCRIPTION = 'Returns the newest articles.';
+
+ public function collectData() {
+ $url = self::URI . 'articles/feed';
+ $this->collectExpandableDatas($url);
+ }
+
+ protected function parseItem($newsItem) {
+ $item = parent::parseItem($newsItem);
+
+ // Enable single page mode?
+ if ($this->getInput('single_page_mode') === true) {
+ $item['uri'] .= '?onglet=full';
+ }
+
+ // If a session cookie is defined, get the full article
+ $mpsessid = $this->getInput('mpsessid');
+ if (!empty($mpsessid)) {
+ // Set the session cookie
+ $opt = array();
+ $opt[CURLOPT_COOKIE] = 'MPSESSID=' . $mpsessid;
+
+ // Get the page
+ $articlePage = getSimpleHTMLDOM(
+ $newsItem->link . '?onglet=full',
+ array(),
+ $opt);
+
+ // Extract the article content
+ $content = $articlePage->find('div.content-article', 0)->innertext;
+ $content = sanitize($content);
+ $content = defaultLinkTo($content, static::URI);
+ $item['content'] .= $content;
+ }
+
+ return $item;
+ }
+}
diff --git a/bridges/PikabuBridge.php b/bridges/PikabuBridge.php
index af603aca..362b87dc 100644
--- a/bridges/PikabuBridge.php
+++ b/bridges/PikabuBridge.php
@@ -6,6 +6,16 @@ class PikabuBridge extends BridgeAbstract {
const DESCRIPTION = 'Выводит посты по тегу';
const MAINTAINER = 'em92';
+ const PARAMETERS_FILTER = array(
+ 'name' => 'Фильтр',
+ 'type' => 'list',
+ 'values' => array(
+ 'Горячее' => 'hot',
+ 'Свежее' => 'new',
+ ),
+ 'defaultValue' => 'hot'
+ );
+
const PARAMETERS = array(
'По тегу' => array(
'tag' => array(
@@ -13,21 +23,29 @@ class PikabuBridge extends BridgeAbstract {
'exampleValue' => 'it',
'required' => true
),
- 'filter' => array(
- 'name' => 'Фильтр',
- 'type' => 'list',
- 'values' => array(
- 'Горячее' => 'hot',
- 'Свежее' => 'new',
- ),
- 'defaultValue' => 'hot'
- )
+ 'filter' => self::PARAMETERS_FILTER
+ ),
+ 'По сообществу' => array(
+ 'community' => array(
+ 'name' => 'Сообщество',
+ 'exampleValue' => 'linux',
+ 'required' => true
+ ),
+ 'filter' => self::PARAMETERS_FILTER
)
);
+ protected $title = null;
+
public function getURI() {
if ($this->getInput('tag')) {
return self::URI . '/tag/' . rawurlencode($this->getInput('tag')) . '/' . rawurlencode($this->getInput('filter'));
+ } else if ($this->getInput('community')) {
+ $uri = self::URI . '/community/' . rawurlencode($this->getInput('community'));
+ if ($this->getInput('filter') != 'hot') {
+ $uri .= '/' . rawurlencode($this->getInput('filter'));
+ }
+ return $uri;
} else {
return parent::getURI();
}
@@ -38,10 +56,10 @@ class PikabuBridge extends BridgeAbstract {
}
public function getName() {
- if (is_string($this->getInput('tag'))) {
- return $this->getInput('tag') . ' - ' . parent::getName();
- } else {
+ if (is_null($this->title)) {
return parent::getName();
+ } else {
+ return $this->title . ' - ' . parent::getName();
}
}
@@ -52,6 +70,8 @@ class PikabuBridge extends BridgeAbstract {
$text_html = iconv('windows-1251', 'utf-8', $text_html);
$html = str_get_html($text_html);
+ $this->title = $html->find('title', 0)->innertext;
+
foreach($html->find('article.story') as $post) {
$time = $post->find('time.story__datetime', 0);
if (is_null($time)) continue;
@@ -67,6 +87,11 @@ class PikabuBridge extends BridgeAbstract {
}
}
+ foreach($post->find('[data-type=gifx]') as $el) {
+ $src = $el->getAttribute('data-source');
+ $el->outertext = '';
+ }
+
foreach($post->find('img') as $img) {
$src = $img->getAttribute('src');
if (!$src) {
diff --git a/bridges/QPlayBridge.php b/bridges/QPlayBridge.php
new file mode 100644
index 00000000..f2043267
--- /dev/null
+++ b/bridges/QPlayBridge.php
@@ -0,0 +1,132 @@
+ array(
+ 'program' => array(
+ 'name' => 'Program Name',
+ 'type' => 'text',
+ 'required' => true,
+ ),
+ ),
+ 'Catalog' => array(
+ 'all_pages' => array(
+ 'name' => 'All Pages',
+ 'type' => 'checkbox',
+ 'defaultValue' => false,
+ ),
+ ),
+ );
+
+ public function getIcon() {
+ # This should be the favicon served on `self::URI`
+ return 'https://s3.amazonaws.com/unode1/assets/4957/r3T9Lm9LTLmpAEX6FlSA_apple-touch-icon.png';
+ }
+
+ public function getURI() {
+ switch ($this->queriedContext) {
+ case 'Program':
+ return self::URI . '/programs/' . $this->getInput('program');
+ case 'Catalog':
+ return self::URI . '/catalog';
+ }
+ return parent::getURI();
+ }
+
+ public function getName() {
+ switch ($this->queriedContext) {
+ case 'Program':
+ $html = getSimpleHTMLDOMCached($this->getURI())
+ or returnServerError('Could not load content');
+
+ return $html->find('h1.program--title', 0)->innertext;
+ case 'Catalog':
+ return self::NAME . ' | Programas';
+ }
+
+ return parent::getName();
+ }
+
+ /* This uses the uscreen platform, other sites can adapt this. https://www.uscreen.tv/ */
+ public function collectData() {
+ switch ($this->queriedContext) {
+ case 'Program':
+ $program = $this->getInput('program');
+ $html = getSimpleHTMLDOMCached($this->getURI())
+ or returnServerError('Could not load content');
+
+ foreach($html->find('.cce--thumbnails-video-chapter') as $element) {
+ $cid = $element->getAttribute('data-id');
+ $item['title'] = $element->find('.cce--chapter-title', 0)->innertext;
+ $item['content'] = $element->find('.cce--thumbnails-image-block', 0)
+ . $element->find('.cce--chapter-body', 0)->innertext;
+ $item['uri'] = $this->getURI() . '?cid=' . $cid;
+
+ /* TODO: Suport login credentials? */
+ /* # Get direct video URL */
+ /* $json_source = getContents(self::URI . '/chapters/' . $cid, array('Cookie: _uscreen2_session=???;')) */
+ /* or returnServerError('Could not request chapter JSON'); */
+ /* $json = json_decode($json_source); */
+
+ /* $item['enclosures'] = [$json->fallback]; */
+
+ $this->items[] = $item;
+ }
+
+ break;
+ case 'Catalog':
+ $json_raw = getContents($this->getCatalogURI(1))
+ or returnServerError('Could not load catalog content');
+
+ $json = json_decode($json_raw);
+ $total_pages = $json->total_pages;
+
+ foreach($this->parseCatalogPage($json) as $item) {
+ $this->items[] = $item;
+ }
+
+ if ($this->getInput('all_pages') === true) {
+ foreach(range(2, $total_pages) as $page) {
+ $json_raw = getContents($this->getCatalogURI($page))
+ or returnServerError('Could not load catalog content (all pages)');
+
+ $json = json_decode($json_raw);
+
+ foreach($this->parseCatalogPage($json) as $item) {
+ $this->items[] = $item;
+ }
+ }
+ }
+
+ break;
+ }
+ }
+
+ private function getCatalogURI($page) {
+ return self::URI . '/catalog.json?page=' . $page;
+ }
+
+ private function parseCatalogPage($json) {
+ $items = array();
+
+ foreach($json->records as $record) {
+ $item = array();
+
+ $item['title'] = $record->title;
+ $item['content'] = $record->description
+ . '
Duration: ' . $record->duration . '
';
+ $item['timestamp'] = strtotime($record->release_date);
+ $item['uri'] = self::URI . $record->url;
+ $item['enclosures'] = array(
+ $record->main_poster,
+ );
+
+ $items[] = $item;
+ }
+
+ return $items;
+ }
+}
diff --git a/bridges/RadioMelodieBridge.php b/bridges/RadioMelodieBridge.php
index 03a14a43..fb5aca6e 100644
--- a/bridges/RadioMelodieBridge.php
+++ b/bridges/RadioMelodieBridge.php
@@ -12,11 +12,12 @@ class RadioMelodieBridge extends BridgeAbstract {
public function collectData(){
$html = getSimpleHTMLDOM(self::URI . '/actu/')
or returnServerError('Could not request Radio Melodie.');
- $list = $html->find('div[class=actu_col1]', 0)->children();;
+ $list = $html->find('div[class=displayList]', 0)->children();
foreach($list as $element) {
if($element->tag == 'a') {
$articleURL = self::URI . $element->href;
$article = getSimpleHTMLDOM($articleURL);
+ $textDOM = $article->find('article', 0);
// Initialise arrays
$item = array();
@@ -24,52 +25,50 @@ class RadioMelodieBridge extends BridgeAbstract {
$picture = array();
// Get the Main picture URL
- $picture[] = $this->rewriteImage($article->find('img[id=picturearticle]', 0)->src);
- $audioHTML = $article->find('div[class=sm2-playlist-wrapper]');
+ $picture[] = $this->rewriteImage($article->find('div[id=pictureTitleSupport]', 0)->find('img', 0)->src);
+ $audioHTML = $article->find('audio');
- // Remove the audio placeholder under the Audio player with an