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