1
0
mirror of https://github.com/RSS-Bridge/rss-bridge.git synced 2025-08-17 22:02:09 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Teromene
db25a68072 Revert "[CommonDreamBridge] Promote to secure bridge (fix #777) (#909)"
This reverts commit 8c97953211.
2018-11-05 17:32:28 +01:00
361 changed files with 5038 additions and 36389 deletions

View File

@@ -1,14 +1,8 @@
.git .git
.gitattributes
.github/*
.travis.yml
cache/* cache/*
CONTRIBUTING.md
DEBUG DEBUG
Dockerfile Dockerfile
phpcompatibility.xml
phpcs.xml
phpcs.xml
scalingo.json
tests/*
whitelist.txt whitelist.txt
phpcs.xml
CHANGELOG.md
CONTRIBUTING.md

47
.gitattributes vendored
View File

@@ -20,50 +20,3 @@
*.PDF diff=astextplain *.PDF diff=astextplain
*.rtf diff=astextplain *.rtf diff=astextplain
*.RTF diff=astextplain *.RTF diff=astextplain
# Ignore files in git archive (i.e. GitHub release builds)
## 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
#
# Keep the following lines commented out. Heroku does
# not function if the composer files are ignored during
# export. For more information see
# https://github.com/rss-bridge/rss-bridge/issues/1165
#
# composer.json export-ignore
# composer.lock export-ignore
## Heroku
#
# Keep the following line commented out. Heroku does
# not function if app.json is ignored during export.
# For more information see
# https://github.com/rss-bridge/rss-bridge/issues/1165
#
# app.json export-ignore

View File

@@ -1,64 +0,0 @@
---
name: Bridge request
about: Use this template for requesting a new bridge
title: Bridge request for ...
labels: Bridge-Request
assignees: ''
---
# Bridge request
<!--
This is a bridge request. Start by adding a descriptive title (i.e. `Bridge request for GitHub`). Use the "Preview" button to see a preview of your request. Make sure your request is complete before submitting!
Notice: This comment is only visible to you while you work on your request. Please do not remove any of the lines in the template (you may add your own outside the "<!--" and "- ->" lines!)
-->
## General information
<!--
Please describe what you expect from the bridge. Whenever possible provide sample links and screenshots (you can just paste them here) to express your expectations and help others understand your request. If possible, mark relevant areas in your screenshot. Use the following questions for reference:
-->
- _Host URI for the bridge_ (i.e. `https://github.com`):
- Which information would you like to see?
- How should the information be displayed/formatted?
- Which of the following parameters do you expect?
- [X] Title
- [X] URI (link to the original article)
- [ ] Author
- [ ] Timestamp
- [X] Content (the content of the article)
- [ ] Enclosures (pictures, videos, etc...)
- [ ] Categories (categories, tags, etc...)
## Options
<!--Select options from the list below. Add your own option if one is missing:-->
- [ ] Limit number of returned items
- _Default limit_: 5
- [ ] Load full articles
- _Cache articles_ (articles are stored in a local cache on first request): yes
- _Cache timeout_ (max = 24 hours): 24 hours
- [X] Balance requests (RSS-Bridge uses cached versions to reduce bandwith usage)
- _Timeout_ (default = 5 minutes, max = 24 hours): 5 minutes
<!--Be aware that some options might not be available for your specific request due to technical limitations!-->
<!--
## Additional notes
Keep in mind that opening a request does not guarantee the bridge being implemented! That depends entirely on the interest and time of others to make the bridge for you.
You can also implement your own bridge (with support of the community if needed). Find more information in the [RSS-Bridge Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki/For-developers) developer section.
-->

View File

@@ -1,38 +0,0 @@
---
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.

View File

@@ -1,20 +0,0 @@
---
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.

View File

@@ -1,35 +0,0 @@
name: Lint
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
phpcs:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
tools: phpcs
- run: phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p
phpcompatibility:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['5.6', '7.4']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require dealerdirect/phpcodesniffer-composer-installer
- run: composer global require phpcompatibility/php-compatibility
- run: ~/.composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --warning-severity=0 --extensions=php -p

View File

@@ -1,47 +0,0 @@
name: Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
phpunit6:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['7.0', '7.1', '7.2']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require phpunit/phpunit ^6
- run: phpunit --configuration=phpunit.xml --include-path=lib/
phpunit7:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['7.1', '7.2', '7.3']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require phpunit/phpunit ^7
- run: phpunit --configuration=phpunit.xml --include-path=lib/
phpunit8:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['7.3', '7.4']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require phpunit/phpunit ^8
- run: phpunit --configuration=phpunit.xml --include-path=lib/

7
.gitignore vendored
View File

@@ -236,10 +236,3 @@ config.ini.php
#Builder #Builder
.buildconfig .buildconfig
#Auth
.htaccess
.htpasswd
#Crawler
robots.txt

38
.travis.yml Normal file
View File

@@ -0,0 +1,38 @@
dist: trusty
sudo: false
language: php
install:
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
composer global require squizlabs/PHP_CodeSniffer;
else
pear channel-update pear.php.net;
pear install PHP_CodeSniffer;
fi
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
composer global require phpunit/phpunit ^6;
fi
script:
- phpenv rehash
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
/home/travis/.composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
else
phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
fi
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
phpunit --configuration=phpunit.xml --include-path=lib/;
fi
matrix:
fast_finish: true
include:
- php: 5.6
- php: 7.0
- php: hhvm
- php: nightly
allow_failures:
- php: hhvm
- php: nightly

263
CHANGELOG.md Normal file
View File

@@ -0,0 +1,263 @@
rss-bridge Changelog
===
RSS-Bridge 2017-08-19
==
## General changes
* whitelist: Do case-insensitive whitelist matching
* [FeedExpander] Fix Serialization of 'SimpleXMLElement' is not allowed
* [FeedExpander] Remove whitespace from source content
* [index] Add GET parameter 'q' for search queries
- **Example**: You can now add `&q=Twitter` to load into the search field
* [index] Check permissions for cache folder and whitelist file
* [index] Show bridge options when loading with URL fragment
- **Example**: You can now add `#bridge-Twitter` to load the card with all
parameters visible
* [style] Center search cursor and hide placeholder
* [validation] Fix error on undefined optional numeric value
## Modified bridges
* [DanbooruBridge] Allow descendant classes to override tag collection
* [DribbbleBridge] Add dribble bridge listing last dribble popular shots (#558)
* [FacebookBridge] Fix &amp; in URLs
* [GelbooruBridge] Fix bridge not getting tags correctly
* [GoComicsBridge] Fix for page structure changes (#568)
* [LeBonCoinBridge] Fix bridge is marked executable
* [LWNprevBridge] Fix everchanging url
* [YoutubeBridge] Fix error on certain keywords
* [YoutubeBridge] Fix issues loading playlists
## Removed bridges
* VineBridge
RSS-Bridge 2017-08-03
==
## Important changes
* RSS-Bridge now has [contribution guidelines](CONTRIBUTING.md)
* [phpcs rules](phpcs.xml) follow the [contribution guidelines](CONTRIBUTING.md)
## General changes
* Added a search bar to make searching for bridges easier
* Added user friendly error page for when a bridge fails
* Added caching of extraInfos (name, uri)
* Added an indicator to warn for bridges using HTTP instead of HTTPS
* Various bug fixes and improvements
## Modified bridges
* AllocineFRBridge] Update Faux Raccord link
* [DanbooruBridge] Fix broken URI
* [DuckDuckGoBridge] Disable DuckDuckGo redirects so that the links returned are correct.
* [FacebookBridge] Add option to hide posts with facebook videos
* [FacebookBridge] Add requester languages to HTTP header
* [FacebookBridge] Handle summary posts
* [FacebookBridge] Replace 'novideo' with 'media_type'
* [FilterBridge] Initial implementation of basic title permit and block
* [FlickrTagBridge] Fix and improve bridge by using the FlickrExploreBridge approach
* [GooglePlusPostBridge] Autofix user names
* [GooglePlusPostBridge] Fix bridge implementation
* [GooglePlusPostBridge] Fix content loading
* [InstagramBridge] Add option to filter for videos and pictures
* [LWNprevBridge] full rewrite
* [MangareaderBridge] Fix double forward slashes
* [NasaApodBridge] Use HTTPS instead of HTTP
* [PinterestBridge] Fix checkbox not working
* [PinterestBridge] Fix implementation after DOM changes
* [RTBFBridge] Update URI
* [SexactuBridge] Fix URI and timestamp
* [SexactuBridge] Use most modern version of bridge api and cached pages (#504)
* [ShanaprojectBridge] Don't throw error if timestamp is missing
* [TwitterBridge] Add option to hide retweets
* [TwitterBridge] Avoid empty content caused by new login policy
* [TwitterBridge] Fix double slashes in URI
* [TwitterBridge] Fix missing spaces
* [TwitterBridge] Fix title includes anchors in plaintext format
* [TwitterBridge] ignore promoted tweets
* [TwitterBridge] Optimize returned image sizes
* [TwitterBridge] Show quotes and pictures
* [WebfailBridge] Properly handle gifs (DOM changed)
* [YoutubeBridge] Improve readability of feed contents
* [YoutubeBridge] Improve URL handling in video descriptions
## New bridges
* AmazonBridge
* DiceBridge
* EtsyBridge
* FB2Bridge
* FilterBridge
* FlickrBridge
* GithubSearchBridge
* GoComicsBridge
* KATBridge
* KernelBugTrackerBridge
* MixCloudBridge
* MoinMoinBridge
* RainbowSixSiegeBridge
* SteamBridge
* TheTVDBBridge
* Torrent9Bridge
* UsbekEtRicaBridge
* WikiLeaksBridge
* WordPressPluginUpdateBridge
Alpha 0.2
===
## Important changes
* RSS-Bridge has been [UNLICENSED](UNLICENSE)
* RSS-Bridge is now a community-managed project on [GitHub](https://github.com/rss-bridge/rss-bridge)
* RSS-Bridge now has a [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
* RSS-Bridge now supports [Travis-CI](https://travis-ci.org)
## General changes
* Added [CHANGELOG](CHANGELOG.md) (this file)
* Added [PHP Simple HTML DOM Parser](http://simplehtmldom.sourceforge.net) to [vendor](vendor/simplehtmldom/)
* Added cache purging function (cache will be force-purged after 24 hours or as defined by bridge)
* Added new format [MrssFormat](formats/MrssFormat.php)
* Added parameter `author` - for display of the feed author name - to all formats
* Added new abstraction of the BridgeInterface:
- [FeedExpander](https://github.com/RSS-Bridge/rss-bridge/wiki/Bridge-API)
* Added optional support for proxy usage on each individual bridge
* Added support for [custom bridge parameter](https://github.com/RSS-Bridge/rss-bridge/wiki/BridgeAbstract#format-specifications) (text, number, list, checkbox)
* Changed design of the welcome screen
* Changed design of HtmlFormat
* Changed behavior of debug mode:
- Enable debug mode by placing a file called "DEBUG" in the root folder
- Debug mode automatically disables cache file loading
* Changed implementation of bridges - see [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
- Changed comment-style metadata to constants
- Added support for multiple utilizations per bridge
- Changed the parameter loading algorithm to be loaded by RSS-Bridge core
* Improved checks for PHP version, configuration and extensions
* Many bug fixes
## Modified Bridges
* FlickrExploreBridge
* GoogleSearchBridge
* TwitterBridge
## New Bridges
* ABCTabsBridge
* AcrimedBridge
* AllocineFRBridge
* AnimeUltimeBridge
* Arte7Bridge
* AskfmBridge
* BandcampBridge
* BastaBridge
* BlaguesDeMerdeBridge
* BooruprojectBridge
* CADBridge
* CNETBridge
* CastorusBridge
* CollegeDeFranceBridge
* CommonDreamsBridge
* CopieDoubleBridge
* CourrierInternationalBridge
* CpasbienBridge
* CryptomeBridge
* DailymotionBridge
* DanbooruBridge
* DansTonChatBridge
* DauphineLibereBridge
* DemoBridge
* DeveloppezDotComBridge
* DilbertBridge
* DollbooruBridge
* DuckDuckGoBridge
* EZTVBridge
* EliteDangerousGalnetBridge
* ElsevierBridge
* EstCeQuonMetEnProdBridge
* FacebookBridge
* FierPandaBridge
* FlickrTagBridge
* FootitoBridge
* FourchanBridge
* FuturaSciencesBridge
* GBAtempBridge
* GelbooruBridge
* GiphyBridge
* GithubIssueBridge
* GizmodoBridge
* GooglePlusPostBridge
* HDWallpapersBridge
* HentaiHavenBridge
* IdenticaBridge
* InstagramBridge
* IsoHuntBridge
* JapanExpoBridge
* KonachanBridge
* KoreusBridge
* KununuBridge
* LWNprevBridge
* LeBonCoinBridge
* LegifranceJOBridge
* LeMondeInformatiqueBridge
* LesJoiesDuCodeBridge
* LichessBridge
* LinkedInCompanyBridge
* LolibooruBridge
* MangareaderBridge
* MilbooruBridge
* MoebooruBridge
* MondeDiploBridge
* MsnMondeBridge
* MspabooruBridge
* NasaApodBridge
* NeuviemeArtBridge
* NextInpactBridge
* NextgovBridge
* NiceMatinBridge
* NovelUpdatesBridge
* OpenClassroomsBridge
* ParuVenduImmoBridge
* PickyWallpapersBridge
* PinterestBridge
* PlanetLibreBridge
* RTBFBridge
* ReadComicsBridge
* Releases3DSBridge
* ReporterreBridge
* Rue89Bridge
* Rule34Bridge
* Rule34pahealBridge
* SafebooruBridge
* SakugabooruBridge
* ScmbBridge
* ScoopItBridge
* SensCritiqueBridge
* SexactuBridge
* ShanaprojectBridge
* Shimmie2Bridge
* SoundcloudBridge
* StripeAPIChangeLogBridge
* SuperbWallpapersBridge
* T411Bridge
* TagBoardBridge
* TbibBridge
* TheCodingLoveBridge
* TheHackerNewsBridge
* ThePirateBayBridge
* UnsplashBridge
* ViadeoCompanyBridge
* VineBridge
* VkBridge
* WallpaperStopBridge
* WebfailBridge
* WeLiveSecurityBridge
* WhydBridge
* WikipediaBridge
* WordPressBridge
* WorldOfTanksBridge
* XbooruBridge
* YandereBridge
* YoutubeBridge
* ZDNetBridge
Alpha 0.1
===
* First tagged version.
* Includes refactoring.
* Unstable.

View File

@@ -43,7 +43,5 @@ Note that all pull-requests must pass all tests before they can be merged.
* [Use PascalCase for class names](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#use-pascalcase-for-class-names) * [Use PascalCase for class names](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#use-pascalcase-for-class-names)
* [Do not use final statements inside final classes](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-use-final-statements-inside-final-classes) * [Do not use final statements inside final classes](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-use-final-statements-inside-final-classes)
* [Do not override methods to call their parent](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-override-methods-to-call-their-parent) * [Do not override methods to call their parent](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-override-methods-to-call-their-parent)
* [abstract and final declarations MUST precede the visibility declaration](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#abstract-and-final-declarations-must-precede-the-visibility-declaration)
* [static declaration MUST come after the visibility declaration](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#static-declaration-must-come-after-the-visibility-declaration)
* [Casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting) * [Casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting)
* [Do not add spaces when casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting#do-not-add-spaces-when-casting) * [Do not add spaces when casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting#do-not-add-spaces-when-casting)

View File

@@ -1,18 +1,5 @@
FROM php:7-apache FROM ulsmith/alpine-apache-php7
ENV APACHE_DOCUMENT_ROOT=/app COPY ./ /app/public/
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \ RUN chown -R apache:root /app/public
&& apt-get --yes update \
&& apt-get --yes --no-install-recommends install \
zlib1g-dev \
libmemcached-dev \
&& rm -rf /var/lib/apt/lists/* \
&& pecl install memcached \
&& docker-php-ext-enable memcached \
&& 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 \
&& sed -ri -e 's/(MinProtocol\s*=\s*)TLSv1\.2/\1None/' /etc/ssl/openssl.cnf \
&& sed -ri -e 's/(CipherString\s*=\s*DEFAULT)@SECLEVEL=2/\1/' /etc/ssl/openssl.cnf
COPY --chown=www-data:www-data ./ /app/

270
README.md
View File

@@ -1,8 +1,8 @@
![RSS-Bridge](static/logo_600px.png) rss-bridge
=== ===
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE) [![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg?logo=github)](https://github.com/rss-bridge/rss-bridge/releases/latest) [![Debian Release](https://img.shields.io/badge/dynamic/json.svg?logo=debian&label=debian%20release&url=https%3A%2F%2Fsources.debian.org%2Fapi%2Fsrc%2Frss-bridge%2F&query=%24.versions%5B0%5D.version&colorB=blue)](https://tracker.debian.org/pkg/rss-bridge) [![Guix Release](https://img.shields.io/badge/guix%20release-unknown-blue.svg)](https://www.gnu.org/software/guix/packages/R/) [![Actions Status](https://img.shields.io/github/workflow/status/RSS-Bridge/rss-bridge/Tests/master?label=GitHub%20Actions&logo=github)](https://github.com/RSS-Bridge/rss-bridge/actions) [![Docker Build Status](https://img.shields.io/docker/cloud/build/rssbridge/rss-bridge?logo=docker)](https://hub.docker.com/r/rssbridge/rss-bridge/) [![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE) [![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg)](https://github.com/rss-bridge/rss-bridge/releases/latest) [![Debian Release](https://img.shields.io/badge/dynamic/json.svg?label=debian%20release&url=https%3A%2F%2Fsources.debian.org%2Fapi%2Fsrc%2Frss-bridge%2F&query=%24.versions%5B0%5D.version&colorB=blue)](https://tracker.debian.org/pkg/rss-bridge) [![Guix Release](https://img.shields.io/badge/guix%20release-unknown-light--gray.svg)](https://www.gnu.org/software/guix/packages/R/) [![Build Status](https://travis-ci.org/RSS-Bridge/rss-bridge.svg?branch=master)](https://travis-ci.org/RSS-Bridge/rss-bridge) [![Docker Build Status](https://img.shields.io/docker/build/rssbridge/rss-bridge.svg)](https://hub.docker.com/r/rssbridge/rss-bridge/)
RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one. It can be used on webservers or as a stand-alone application in CLI mode. RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites which don't have one. It can be used on webservers or as stand alone application in CLI mode.
**Important**: RSS-Bridge is __not__ a feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators). **Important**: RSS-Bridge is __not__ a feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators).
@@ -15,6 +15,7 @@ Supported sites/pages (examples)
* `DuckDuckGo`: Most recent results from [DuckDuckGo.com](https://duckduckgo.com/) * `DuckDuckGo`: Most recent results from [DuckDuckGo.com](https://duckduckgo.com/)
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/) * `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/)
* `FlickrExplore` : [Latest interesting images](http://www.flickr.com/explore) from Flickr * `FlickrExplore` : [Latest interesting images](http://www.flickr.com/explore) from Flickr
* `GooglePlus` : Most recent posts of user timeline
* `GoogleSearch` : Most recent results from Google Search * `GoogleSearch` : Most recent results from Google Search
* `Identi.ca` : Identica user timeline (Should be compatible with other Pump.io instances) * `Identi.ca` : Identica user timeline (Should be compatible with other Pump.io instances)
* `Instagram`: Most recent photos from an Instagram user * `Instagram`: Most recent photos from an Instagram user
@@ -65,8 +66,6 @@ RSS-Bridge requires PHP 5.6 or higher with following extensions enabled:
- [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php) - [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php)
- [`curl`](https://secure.php.net/manual/en/book.curl.php) - [`curl`](https://secure.php.net/manual/en/book.curl.php)
- [`json`](https://secure.php.net/manual/en/book.json.php) - [`json`](https://secure.php.net/manual/en/book.json.php)
- [`filter`](https://secure.php.net/manual/en/book.filter.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) Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
@@ -77,7 +76,7 @@ RSS-Bridge allows you to take full control over which bridges are displayed to t
Find more information on the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting) Find more information on the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting)
**Notice**: By default, RSS-Bridge will only show a small subset of bridges. Make sure to read up on [whitelisting](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting) to unlock the full potential of RSS-Bridge! **Notice**: By default RSS-Bridge will only show a small subset of bridges. Make sure to read up on [whitelisting](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting) to unlock the full potential of RSS-Bridge!
Deploy Deploy
=== ===
@@ -85,7 +84,7 @@ Deploy
Thanks to the community, hosting your own instance of RSS-Bridge is as easy as clicking a button! 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 on Scalingo](https://cdn.scalingo.com/deploy/button.svg)](https://my.scalingo.com/deploy?source=https://github.com/sebsauvage/rss-bridge)
[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) [![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)
Getting involved Getting involved
=== ===
@@ -111,178 +110,88 @@ Use this script to generate the list automatically (using the GitHub API):
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8 https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
--> -->
* [16mhz](https://github.com/16mhz) * [16mhz](https://api.github.com/users/16mhz)
* [adamchainz](https://github.com/adamchainz) * [Ahiles3005](https://api.github.com/users/Ahiles3005)
* [Ahiles3005](https://github.com/Ahiles3005) * [Albirew](https://api.github.com/users/Albirew)
* [akirk](https://github.com/akirk) * [AmauryCarrade](https://api.github.com/users/AmauryCarrade)
* [Albirew](https://github.com/Albirew) * [ArthurHoaro](https://api.github.com/users/ArthurHoaro)
* [aledeg](https://github.com/aledeg) * [Astalaseven](https://api.github.com/users/Astalaseven)
* [alex73](https://github.com/alex73) * [Astyan-42](https://api.github.com/users/Astyan-42)
* [alexAubin](https://github.com/alexAubin) * [Daiyousei](https://api.github.com/users/Daiyousei)
* [AmauryCarrade](https://github.com/AmauryCarrade) * [Djuuu](https://api.github.com/users/Djuuu)
* [AntoineTurmel](https://github.com/AntoineTurmel) * [Draeli](https://api.github.com/users/Draeli)
* [arnd-s](https://github.com/arnd-s) * [EtienneM](https://api.github.com/users/EtienneM)
* [ArthurHoaro](https://github.com/ArthurHoaro) * [Frenzie](https://api.github.com/users/Frenzie)
* [Astalaseven](https://github.com/Astalaseven) * [Ginko-Aloe](https://api.github.com/users/Ginko-Aloe)
* [Astyan-42](https://github.com/Astyan-42) * [Glandos](https://api.github.com/users/Glandos)
* [AxorPL](https://github.com/AxorPL) * [GregThib](https://api.github.com/users/GregThib)
* [ayacoo](https://github.com/ayacoo) * [Grummfy](https://api.github.com/users/Grummfy)
* [az5he6ch](https://github.com/az5he6ch) * [JackNUMBER](https://api.github.com/users/JackNUMBER)
* [b1nj](https://github.com/b1nj) * [JeremyRand](https://api.github.com/users/JeremyRand)
* [benasse](https://github.com/benasse) * [Jocker666z](https://api.github.com/users/Jocker666z)
* [Binnette](https://github.com/Binnette) * [LogMANOriginal](https://api.github.com/users/LogMANOriginal)
* [captn3m0](https://github.com/captn3m0) * [MonsieurPoutounours](https://api.github.com/users/MonsieurPoutounours)
* [chemel](https://github.com/chemel) * [ORelio](https://api.github.com/users/ORelio)
* [Chouchen](https://github.com/Chouchen) * [PaulVayssiere](https://api.github.com/users/PaulVayssiere)
* [ckiw](https://github.com/ckiw) * [Piranhaplant](https://api.github.com/users/Piranhaplant)
* [cn-tools](https://github.com/cn-tools) * [Riduidel](https://api.github.com/users/Riduidel)
* [cnlpete](https://github.com/cnlpete) * [Strubbl](https://api.github.com/users/Strubbl)
* [corenting](https://github.com/corenting) * [TheRadialActive](https://api.github.com/users/TheRadialActive)
* [couraudt](https://github.com/couraudt) * [TwizzyDizzy](https://api.github.com/users/TwizzyDizzy)
* [csisoap](https://github.com/csisoap) * [WalterBarrett](https://api.github.com/users/WalterBarrett)
* [cyberjacob](https://github.com/cyberjacob) * [ZeNairolf](https://api.github.com/users/ZeNairolf)
* [da2x](https://github.com/da2x) * [adamchainz](https://api.github.com/users/adamchainz)
* [Daiyousei](https://github.com/Daiyousei) * [aledeg](https://api.github.com/users/aledeg)
* [dawidsowa](https://github.com/dawidsowa) * [alexAubin](https://api.github.com/users/alexAubin)
* [DevonHess](https://github.com/DevonHess) * [az5he6ch](https://api.github.com/users/az5he6ch)
* [disk0x](https://github.com/disk0x) * [b1nj](https://api.github.com/users/b1nj)
* [DJCrashdummy](https://github.com/DJCrashdummy) * [benasse](https://api.github.com/users/benasse)
* [Djuuu](https://github.com/Djuuu) * [captn3m0](https://api.github.com/users/captn3m0)
* [DnAp](https://github.com/DnAp) * [chemel](https://api.github.com/users/chemel)
* [dominik-th](https://github.com/dominik-th) * [ckiw](https://api.github.com/users/ckiw)
* [Draeli](https://github.com/Draeli) * [cnlpete](https://api.github.com/users/cnlpete)
* [Dreckiger-Dan](https://github.com/Dreckiger-Dan) * [corenting](https://api.github.com/users/corenting)
* [drego85](https://github.com/drego85) * [da2x](https://api.github.com/users/da2x)
* [drklee3](https://github.com/drklee3) * [eMerzh](https://api.github.com/users/eMerzh)
* [em92](https://github.com/em92) * [em92](https://api.github.com/users/em92)
* [eMerzh](https://github.com/eMerzh) * [griffaurel](https://api.github.com/users/griffaurel)
* [EtienneM](https://github.com/EtienneM) * [hunhejj](https://api.github.com/users/hunhejj)
* [fanch317](https://github.com/fanch317) * [j0k3r](https://api.github.com/users/j0k3r)
* [fivefilters](https://github.com/fivefilters) * [jdigilio](https://api.github.com/users/jdigilio)
* [floviolleau](https://github.com/floviolleau) * [kranack](https://api.github.com/users/kranack)
* [fluffy-critter](https://github.com/fluffy-critter) * [kraoc](https://api.github.com/users/kraoc)
* [Frenzie](https://github.com/Frenzie) * [laBecasse](https://api.github.com/users/laBecasse)
* [fulmeek](https://github.com/fulmeek) * [lagaisse](https://api.github.com/users/lagaisse)
* [ggiessen](https://github.com/ggiessen) * [lalannev](https://api.github.com/users/lalannev)
* [Ginko-Aloe](https://github.com/Ginko-Aloe) * [ldidry](https://api.github.com/users/ldidry)
* [Glandos](https://github.com/Glandos) * [m0zes](https://api.github.com/users/m0zes)
* [gloony](https://github.com/gloony) * [matthewseal](https://api.github.com/users/matthewseal)
* [GregThib](https://github.com/GregThib) * [mcbyte-it](https://api.github.com/users/mcbyte-it)
* [griffaurel](https://github.com/griffaurel) * [mdemoss](https://api.github.com/users/mdemoss)
* [Grummfy](https://github.com/Grummfy) * [melangue](https://api.github.com/users/melangue)
* [gsantner](https://github.com/gsantner) * [metaMMA](https://api.github.com/users/metaMMA)
* [guigot](https://github.com/guigot) * [mickael-bertrand](https://api.github.com/users/mickael-bertrand)
* [hollowleviathan](https://github.com/hollowleviathan) * [mitsukarenai](https://api.github.com/users/mitsukarenai)
* [hpacleb](https://github.com/hpacleb) * [mro](https://api.github.com/users/mro)
* [hunhejj](https://github.com/hunhejj) * [mxmehl](https://api.github.com/users/mxmehl)
* [husim0](https://github.com/husim0) * [nel50n](https://api.github.com/users/nel50n)
* [IceWreck](https://github.com/IceWreck) * [niawag](https://api.github.com/users/niawag)
* [j0k3r](https://github.com/j0k3r) * [pellaeon](https://api.github.com/users/pellaeon)
* [JackNUMBER](https://github.com/JackNUMBER) * [pit-fgfjiudghdf](https://api.github.com/users/pit-fgfjiudghdf)
* [jacquesh](https://github.com/jacquesh) * [pitchoule](https://api.github.com/users/pitchoule)
* [JasonGhent](https://github.com/JasonGhent) * [pmaziere](https://api.github.com/users/pmaziere)
* [jcgoette](https://github.com/jcgoette) * [prysme01](https://api.github.com/users/prysme01)
* [jdesgats](https://github.com/jdesgats) * [quentinus95](https://api.github.com/users/quentinus95)
* [jdigilio](https://github.com/jdigilio) * [qwertygc](https://api.github.com/users/qwertygc)
* [JeremyRand](https://github.com/JeremyRand) * [regisenguehard](https://api.github.com/users/regisenguehard)
* [JimDog546](https://github.com/JimDog546) * [rogerdc](https://api.github.com/users/rogerdc)
* [Jocker666z](https://github.com/Jocker666z) * [sebsauvage](https://api.github.com/users/sebsauvage)
* [johnnygroovy](https://github.com/johnnygroovy) * [sublimz](https://api.github.com/users/sublimz)
* [johnpc](https://github.com/johnpc) * [sysadminstory](https://api.github.com/users/sysadminstory)
* [joni1993](https://github.com/joni1993) * [tameroski](https://api.github.com/users/tameroski)
* [joshcoales](https://github.com/joshcoales) * [teromene](https://api.github.com/users/teromene)
* [klimplant](https://github.com/klimplant) * [triatic](https://api.github.com/users/triatic)
* [kolarcz](https://github.com/kolarcz) * [wtuuju](https://api.github.com/users/wtuuju)
* [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)
* [Leomaradan](https://github.com/Leomaradan)
* [liamka](https://github.com/liamka)
* [Limero](https://github.com/Limero)
* [LogMANOriginal](https://github.com/LogMANOriginal)
* [lorenzos](https://github.com/lorenzos)
* [lukasklinger](https://github.com/lukasklinger)
* [m0zes](https://github.com/m0zes)
* [matthewseal](https://github.com/matthewseal)
* [mcbyte-it](https://github.com/mcbyte-it)
* [mdemoss](https://github.com/mdemoss)
* [melangue](https://github.com/melangue)
* [metaMMA](https://github.com/metaMMA)
* [mibe](https://github.com/mibe)
* [mightymt](https://github.com/mightymt)
* [mitsukarenai](https://github.com/mitsukarenai)
* [Monocularity](https://github.com/Monocularity)
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
* [mr-flibble](https://github.com/mr-flibble)
* [mro](https://github.com/mro)
* [mschwld](https://github.com/mschwld)
* [mxmehl](https://github.com/mxmehl)
* [nel50n](https://github.com/nel50n)
* [niawag](https://github.com/niawag)
* [Niehztog](https://github.com/Niehztog)
* [Nono-m0le](https://github.com/Nono-m0le)
* [ObsidianWitch](https://github.com/ObsidianWitch)
* [OliverParoczai](https://github.com/OliverParoczai)
* [Ololbu](https://github.com/Ololbu)
* [ORelio](https://github.com/ORelio)
* [otakuf](https://github.com/otakuf)
* [Park0](https://github.com/Park0)
* [Paroleen](https://github.com/Paroleen)
* [PaulVayssiere](https://github.com/PaulVayssiere)
* [pellaeon](https://github.com/pellaeon)
* [PeterDaveHello](https://github.com/PeterDaveHello)
* [Peterr-K](https://github.com/Peterr-K)
* [Piranhaplant](https://github.com/Piranhaplant)
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
* [pitchoule](https://github.com/pitchoule)
* [pmaziere](https://github.com/pmaziere)
* [Pofilo](https://github.com/Pofilo)
* [prysme01](https://github.com/prysme01)
* [Qluxzz](https://github.com/Qluxzz)
* [quentinus95](https://github.com/quentinus95)
* [rakoo](https://github.com/rakoo)
* [RawkBob](https://github.com/RawkBob)
* [regisenguehard](https://github.com/regisenguehard)
* [Riduidel](https://github.com/Riduidel)
* [rogerdc](https://github.com/rogerdc)
* [Roliga](https://github.com/Roliga)
* [ronansalmon](https://github.com/ronansalmon)
* [rremizov](https://github.com/rremizov)
* [sebsauvage](https://github.com/sebsauvage)
* [shutosg](https://github.com/shutosg)
* [simon816](https://github.com/simon816)
* [Simounet](https://github.com/Simounet)
* [somini](https://github.com/somini)
* [squeek502](https://github.com/squeek502)
* [stjohnjohnson](https://github.com/stjohnjohnson)
* [Strubbl](https://github.com/Strubbl)
* [sublimz](https://github.com/sublimz)
* [sunchaserinfo](https://github.com/sunchaserinfo)
* [SuperSandro2000](https://github.com/SuperSandro2000)
* [sysadminstory](https://github.com/sysadminstory)
* [t0stiman](https://github.com/t0stiman)
* [tameroski](https://github.com/tameroski)
* [teromene](https://github.com/teromene)
* [tgkenney](https://github.com/tgkenney)
* [thefranke](https://github.com/thefranke)
* [ThePadawan](https://github.com/ThePadawan)
* [TheRadialActive](https://github.com/TheRadialActive)
* [theScrabi](https://github.com/theScrabi)
* [thezeroalpha](https://github.com/thezeroalpha)
* [TitiTestScalingo](https://github.com/TitiTestScalingo)
* [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)
* [yamanq](https://github.com/yamanq)
* [yardenac](https://github.com/yardenac)
* [ymeister](https://github.com/ymeister)
* [ZeNairolf](https://github.com/ZeNairolf)
Licenses Licenses
=== ===
@@ -291,7 +200,6 @@ The source code for RSS-Bridge is [Public Domain](UNLICENSE).
RSS-Bridge uses third party libraries with their own license: RSS-Bridge uses third party libraries with their own license:
* [`Parsedown`](https://github.com/erusev/parsedown) licensed under the [MIT License](http://opensource.org/licenses/MIT)
* [`PHP Simple HTML DOM Parser`](http://simplehtmldom.sourceforge.net/) licensed under the [MIT License](http://opensource.org/licenses/MIT) * [`PHP Simple HTML DOM Parser`](http://simplehtmldom.sourceforge.net/) licensed under the [MIT License](http://opensource.org/licenses/MIT)
* [`php-urljoin`](https://github.com/fluffy-critter/php-urljoin) licensed under the [MIT License](http://opensource.org/licenses/MIT) * [`php-urljoin`](https://github.com/fluffy-critter/php-urljoin) licensed under the [MIT License](http://opensource.org/licenses/MIT)
@@ -313,6 +221,6 @@ You're not social when you hamper sharing by removing feeds. You're happy to hav
We want to share with friends, using open protocols: RSS, Atom, XMPP, whatever. Because no one wants to have *your* service with *your* applications using *your* API force-feeding them. Friends must be free to choose whatever software and service they want. We want to share with friends, using open protocols: RSS, Atom, XMPP, whatever. Because no one wants to have *your* service with *your* applications using *your* API force-feeding them. Friends must be free to choose whatever software and service they want.
We are rebuilding bridges you have willfully destroyed. We are rebuilding bridges you have wilfully destroyed.
Get your shit together: Put RSS/Atom back in. Get your shit together: Put RSS/Atom back in.

View File

@@ -1,136 +0,0 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
/**
* Checks if the website for a given bridge is reachable.
*
* **Remarks**
* - This action is only available in debug mode.
* - Returns the bridge status as Json-formatted string.
* - Returns an error if the bridge is not whitelisted.
* - Returns a responsive web page that automatically checks all whitelisted
* bridges (using JavaScript) if no bridge is specified.
*/
class ConnectivityAction extends ActionAbstract {
public function execute() {
if(!Debug::isEnabled()) {
returnError('This action is only available in debug mode!');
}
if(!isset($this->userData['bridge'])) {
$this->returnEntryPage();
return;
}
$bridgeName = $this->userData['bridge'];
$this->reportBridgeConnectivity($bridgeName);
}
/**
* Generates a report about the bridge connectivity status and sends it back
* to the user.
*
* The report is generated as Json-formatted string in the format
* {
* "bridge": "<bridge-name>",
* "successful": true/false
* }
*
* @param string $bridgeName Name of the bridge to generate the report for
* @return void
*/
private function reportBridgeConnectivity($bridgeName) {
$bridgeFac = new \BridgeFactory();
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
if(!$bridgeFac->isWhitelisted($bridgeName)) {
header('Content-Type: text/html');
returnServerError('Bridge is not whitelisted!');
}
header('Content-Type: text/json');
$retVal = array(
'bridge' => $bridgeName,
'successful' => false,
'http_code' => 200,
);
$bridge = $bridgeFac->create($bridgeName);
if($bridge === false) {
echo json_encode($retVal);
return;
}
$curl_opts = array(
CURLOPT_CONNECTTIMEOUT => 5
);
try {
$reply = getContents($bridge::URI, array(), $curl_opts, true);
if($reply) {
$retVal['successful'] = true;
if (isset($reply['header'])) {
if (strpos($reply['header'], 'HTTP/1.1 301 Moved Permanently') !== false) {
$retVal['http_code'] = 301;
}
}
}
} catch(Exception $e) {
$retVal['successful'] = false;
}
echo json_encode($retVal);
}
private function returnEntryPage() {
echo <<<EOD
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="static/bootstrap.min.css">
<link
rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
crossorigin="anonymous">
<link rel="stylesheet" href="static/connectivity.css">
<script src="static/connectivity.js" type="text/javascript"></script>
</head>
<body>
<div id="main-content" class="container">
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div id="status-message" class="sticky-top alert alert-primary alert-dismissible fade show" role="alert">
<i id="status-icon" class="fas fa-sync"></i>
<span>...</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close" onclick="stopConnectivityChecks()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<input type="text" class="form-control" id="search" onkeyup="search()" placeholder="Search for bridge..">
</div>
</body>
</html>
EOD;
}
}

View File

@@ -1,53 +0,0 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class DetectAction extends ActionAbstract {
public function execute() {
$targetURL = $this->userData['url']
or returnClientError('You must specify a url!');
$format = $this->userData['format']
or returnClientError('You must specify a format!');
$bridgeFac = new \BridgeFactory();
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
foreach($bridgeFac->getBridgeNames() as $bridgeName) {
if(!$bridgeFac->isWhitelisted($bridgeName)) {
continue;
}
$bridge = $bridgeFac->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);
}
}

View File

@@ -1,259 +0,0 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class DisplayAction extends ActionAbstract {
private function get_return_code($error) {
$returnCode = $error->getCode();
if ($returnCode === 301 || $returnCode === 302) {
# Don't pass redirect codes to the exterior
$returnCode = 508;
}
return $returnCode;
}
public function execute() {
$bridge = array_key_exists('bridge', $this->userData) ? $this->userData['bridge'] : null;
$format = $this->userData['format']
or returnClientError('You must specify a format!');
$bridgeFac = new \BridgeFactory();
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
// whitelist control
if(!$bridgeFac->isWhitelisted($bridge)) {
throw new \Exception('This bridge is not whitelisted', 401);
die;
}
// Data retrieval
$bridge = $bridgeFac->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
$cacheFac = new CacheFactory();
$cacheFac->setWorkingDir(PATH_LIB_CACHES);
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
$cache->setScope('');
$cache->purgeCache(86400); // 24 hours
$cache->setKey($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->loadConfiguration();
$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);
if(logBridgeError($bridge::NAME, $e->getCode()) >= Configuration::getConfig('error', 'report_limit')) {
if(Configuration::getConfig('error', 'output') === 'feed') {
$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;
} elseif(Configuration::getConfig('error', 'output') === 'http') {
header('Content-Type: text/html', true, $this->get_return_code($e));
die(buildTransformException($e, $bridge));
}
}
} catch(Exception $e) {
error_log($e);
if(logBridgeError($bridge::NAME, $e->getCode()) >= Configuration::getConfig('error', 'report_limit')) {
if(Configuration::getConfig('error', 'output') === 'feed') {
$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;
} elseif(Configuration::getConfig('error', 'output') === 'http') {
header('Content-Type: text/html', true, $this->get_return_code($e));
die(buildTransformException($e, $bridge));
}
}
}
// Store data in cache
$cache->saveData(array(
'items' => array_map(function($i){ return $i->toArray(); }, $items),
'extraInfos' => $infos
));
}
// Data transformation
try {
$formatFac = new FormatFactory();
$formatFac->setWorkingDir(PATH_LIB_FORMATS);
$format = $formatFac->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));
}
}
}

View File

@@ -1,56 +0,0 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class ListAction extends ActionAbstract {
public function execute() {
$list = new StdClass();
$list->bridges = array();
$list->total = 0;
$bridgeFac = new \BridgeFactory();
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
foreach($bridgeFac->getBridgeNames() as $bridgeName) {
$bridge = $bridgeFac->create($bridgeName);
if($bridge === false) { // Broken bridge, show as inactive
$list->bridges[$bridgeName] = array(
'status' => 'inactive'
);
continue;
}
$status = $bridgeFac->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);
}
}

View File

@@ -1,8 +0,0 @@
{
"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"]
}

View File

@@ -1,121 +0,0 @@
<?php
class AO3Bridge extends BridgeAbstract {
const NAME = 'AO3';
const URI = 'https://archiveofourown.org/';
const CACHE_TIMEOUT = 1800;
const DESCRIPTION = 'Returns works or chapters from Archive of Our Own';
const MAINTAINER = 'Obsidienne';
const PARAMETERS = array(
'List' => array(
'url' => array(
'name' => 'url',
'required' => true,
// Example: F/F tag, complete works only
'exampleValue' => self::URI
. 'works?work_search[complete]=T&tag_id=F*s*F',
),
),
'Bookmarks' => array(
'user' => array(
'name' => 'user',
'required' => true,
// Example: Nyaaru's bookmarks
'exampleValue' => 'Nyaaru',
),
),
'Work' => array(
'id' => array(
'name' => 'id',
'required' => true,
// Example: latest chapters from A Better Past by LysSerris
'exampleValue' => '18181853',
),
)
);
// Feed for lists of works (e.g. recent works, search results, filtered tags,
// bookmarks, series, collections).
private function collectList($url) {
$html = getSimpleHTMLDOM($url)
or returnServerError('could not request AO3');
$html = defaultLinkTo($html, self::URI);
foreach($html->find('.index.group > li') as $element) {
$item = array();
$title = $element->find('div h4 a', 0);
if (!isset($title)) continue; // discard deleted works
$item['title'] = $title->plaintext;
$item['content'] = $element;
$item['uri'] = $title->href;
$strdate = $element->find('div p.datetime', 0)->plaintext;
$item['timestamp'] = strtotime($strdate);
$chapters = $element->find('dl dd.chapters', 0);
// bookmarked series and external works do not have a chapters count
$chapters = (isset($chapters) ? $chapters->plaintext : 0);
$item['uid'] = $item['uri'] . "/$strdate/$chapters";
$this->items[] = $item;
}
}
// Feed for recent chapters of a specific work.
private function collectWork($id) {
$url = self::URI . "/works/$id/navigate";
$html = getSimpleHTMLDOM($url)
or returnServerError('could not request AO3');
$html = defaultLinkTo($html, self::URI);
$this->title = $html->find('h2 a', 0)->plaintext;
foreach($html->find('ol.index.group > li') as $element) {
$item = array();
$item['title'] = $element->find('a', 0)->plaintext;
$item['content'] = $element;
$item['uri'] = $element->find('a', 0)->href;
$strdate = $element->find('span.datetime', 0)->plaintext;
$strdate = str_replace('(', '', $strdate);
$strdate = str_replace(')', '', $strdate);
$item['timestamp'] = strtotime($strdate);
$item['uid'] = $item['uri'] . "/$strdate";
$this->items[] = $item;
}
$this->items = array_reverse($this->items);
}
public function collectData() {
switch($this->queriedContext) {
case 'Bookmarks':
$user = $this->getInput('user');
$this->title = $user;
$url = self::URI
. '/users/' . $user
. '/bookmarks?bookmark_search[sort_column]=bookmarkable_date';
return $this->collectList($url);
case 'List': return $this->collectList(
$this->getInput('url')
);
case 'Work': return $this->collectWork(
$this->getInput('id')
);
}
}
public function getName() {
$name = parent::getName() . " $this->queriedContext";
if (isset($this->title)) $name .= " - $this->title";
return $name;
}
public function getIcon() {
return self::URI . '/favicon.ico';
}
}

View File

@@ -1,57 +0,0 @@
<?php
class ASRockNewsBridge extends BridgeAbstract {
const NAME = 'ASRock News Bridge';
const URI = 'https://www.asrock.com';
const DESCRIPTION = 'Returns latest news articles';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array();
const CACHE_TIMEOUT = 3600; // 1 hour
public function collectData() {
$html = getSimpleHTMLDOM(self::URI . '/news/index.asp')
or returnServerError('Could not request: ' . self::URI . '/news/index.asp');
$html = defaultLinkTo($html, self::URI . '/news/');
foreach($html->find('div.inner > a') as $index => $a) {
$item = array();
$articlePath = $a->href;
$articlePageHtml = getSimpleHTMLDOMCached($articlePath, self::CACHE_TIMEOUT)
or returnServerError('Could not request: ' . $articlePath);
$articlePageHtml = defaultLinkTo($articlePageHtml, self::URI);
$contents = $articlePageHtml->find('div.Contents', 0);
$item['uri'] = $articlePath;
$item['title'] = $contents->find('h5', 0)->innertext;
$contents->find('h5', 0)->outertext = '';
$item['content'] = $contents->innertext;
$item['timestamp'] = $this->extractDate($a->plaintext);
$item['enclosures'][] = $a->find('img', 0)->src;
$this->items[] = $item;
if (count($this->items) >= 10) {
break;
}
}
}
private function extractDate($text) {
$dateRegex = '/^([0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2})/';
$text = trim($text);
if (preg_match($dateRegex, $text, $matches)) {
return $matches[1];
}
return '';
}
}

View File

@@ -21,4 +21,5 @@ class AcrimedBridge extends FeedExpander {
return $item; return $item;
} }
} }

View File

@@ -1,54 +0,0 @@
<?php
class AirBreizhBridge extends BridgeAbstract {
const MAINTAINER = 'fanch317';
const NAME = 'Air Breizh';
const URI = 'https://www.airbreizh.asso.fr/';
const DESCRIPTION = 'Returns newests publications on Air Breizh';
const PARAMETERS = array(
'Publications' => array(
'theme' => array(
'name' => 'Thematique',
'type' => 'list',
'values' => array(
'Tout' => '',
'Rapport d\'activite' => 'rapport-dactivite',
'Etude' => 'etudes',
'Information' => 'information',
'Autres documents' => 'autres-documents',
'Plan Régional de Surveillance de la qualité de lair' => 'prsqa',
'Transport' => 'transport'
)
)
)
);
public function getIcon() {
return 'https://www.airbreizh.asso.fr/voy_content/uploads/2017/11/favicon.png';
}
public function collectData(){
$html = '';
$html = getSimpleHTMLDOM(static::URI . 'publications/?fwp_publications_thematiques=' . $this->getInput('theme'))
or returnClientError('No results for this query.');
foreach ($html->find('article') as $article) {
$item = array();
// Title
$item['title'] = $article->find('h2', 0)->plaintext;
// Author
$item['author'] = 'Air Breizh';
// Image
$imagelink = $article->find('.card__image', 0)->find('img', 0)->getAttribute('src');
// Content preview
$item['content'] = '<img src="' . $imagelink . '" />
<br/>'
. $article->find('.card__text', 0)->plaintext;
// URL
$item['uri'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href');
// ID
$item['id'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href');
$this->items[] = $item;
}
}
}

View File

@@ -1,74 +0,0 @@
<?php
class AlbionOnlineBridge extends BridgeAbstract {
const NAME = 'Albion Online Changelog';
const MAINTAINER = 'otakuf';
const URI = 'https://albiononline.com';
const DESCRIPTION = 'Returns the changes made to the Albion Online';
const CACHE_TIMEOUT = 3600; // 60min
const PARAMETERS = array( array(
'postcount' => array(
'name' => 'Limit',
'type' => 'number',
'title' => 'Maximum number of items to return',
'defaultValue' => 5,
),
'language' => array(
'name' => 'Language',
'type' => 'list',
'values' => array(
'English' => 'en',
'Deutsch' => 'de',
'Polski' => 'pl',
'Français' => 'fr',
'Русский' => 'ru',
'Português' => 'pt',
'Español' => 'es',
),
'title' => 'Language of changelog posts',
'defaultValue' => 'en',
),
'full' => array(
'name' => 'Full changelog',
'type' => 'checkbox',
'required' => false,
'title' => 'Enable to receive the full changelog post for each item'
),
));
public function collectData() {
$api = 'https://albiononline.com/';
// Example: https://albiononline.com/en/changelog/1/5
$url = $api . $this->getInput('language') . '/changelog/1/' . $this->getInput('postcount');
$html = getSimpleHTMLDOM($url)
or returnServerError('Unable to get changelog data from "' . $url . '"!');
foreach ($html->find('li') as $data) {
$item = array();
$item['uri'] = self::URI . $data->find('a', 0)->getAttribute('href');
$item['title'] = trim(explode('|', $data->find('span', 0)->plaintext)[0]);
// Time below work only with en lang. Need to think about solution. May be separate request like getFullChangelog, but to english list for all language
//print_r( date_parse_from_format( 'M j, Y' , 'Sep 9, 2020') );
//$item['timestamp'] = $this->extractDate($a->plaintext);
$item['author'] = 'albiononline.com';
if($this->getInput('full')) {
$item['content'] = $this->getFullChangelog($item['uri']);
} else {
//$item['content'] = trim(preg_replace('/\s+/', ' ', $data->find('span', 0)->plaintext));
// Just use title, no info at all or use title and date, see above
$item['content'] = $item['title'];
}
$item['uid'] = hash('sha256', $item['title']);
$this->items[] = $item;
}
}
private function getFullChangelog($url) {
$html = getSimpleHTMLDOMCached($url)
or returnServerError('Unable to load changelog post from "' . $url . '"!');
$html = defaultLinkTo($html, self::URI);
return $html->find('div.small-12.columns', 1)->innertext;
}
}

View File

@@ -8,25 +8,15 @@ class AllocineFRBridge extends BridgeAbstract {
const DESCRIPTION = 'Bridge for allocine.fr'; const DESCRIPTION = 'Bridge for allocine.fr';
const PARAMETERS = array( array( const PARAMETERS = array( array(
'category' => array( 'category' => array(
'name' => 'Emission', 'name' => 'category',
'type' => 'list', 'type' => 'list',
'title' => 'Sélectionner l\'emission', 'required' => true,
'exampleValue' => 'Faux Raccord',
'title' => 'Select your category',
'values' => array( 'values' => array(
'Faux Raccord' => 'faux-raccord', 'Faux Raccord' => 'faux-raccord',
'Fanzone' => 'fanzone', 'Top 5' => 'top-5',
'Game In Ciné' => 'game-in-cine', 'Tueurs en Séries' => 'tueurs-en-serie'
'Pour la faire courte' => 'pour-la-faire-courte',
'Home Cinéma' => 'home-cinema',
'PILS - Par Ici Les Sorties' => 'pils-par-ici-les-sorties',
'AlloCiné : l\'émission, sur LeStream' => 'allocine-lemission-sur-lestream',
'Give Me Five' => 'give-me-five',
'Aviez-vous remarqué ?' => 'aviez-vous-remarque',
'Et paf, il est mort' => 'et-paf-il-est-mort',
'The Big Fan Theory' => 'the-big-fan-theory',
'Clichés' => 'cliches',
'Complètement...' => 'completement',
'#Fun Facts' => 'fun-facts',
'Origin Story' => 'origin-story',
) )
) )
)); ));
@@ -34,30 +24,19 @@ class AllocineFRBridge extends BridgeAbstract {
public function getURI(){ public function getURI(){
if(!is_null($this->getInput('category'))) { if(!is_null($this->getInput('category'))) {
$categories = array( switch($this->getInput('category')) {
'faux-raccord' => 'video/programme-12284/saison-37054/', case 'faux-raccord':
'fanzone' => 'video/programme-12298/saison-37059/', $uri = static::URI . 'video/programme-12284/saison-32180/';
'game-in-cine' => 'video/programme-12288/saison-22971/', break;
'pour-la-faire-courte' => 'video/programme-20960/saison-29678/', case 'top-5':
'home-cinema' => 'video/programme-12287/saison-34703/', $uri = static::URI . 'video/programme-12299/saison-29561/';
'pils-par-ici-les-sorties' => 'video/programme-25789/saison-37253/', break;
'allocine-lemission-sur-lestream' => 'video/programme-25123/saison-36067/', case 'tueurs-en-serie':
'give-me-five' => 'video/programme-21919/saison-34518/', $uri = static::URI . 'video/programme-12286/saison-22938/';
'aviez-vous-remarque' => 'video/programme-19518/saison-37084/', break;
'et-paf-il-est-mort' => 'video/programme-25113/saison-36657/',
'the-big-fan-theory' => 'video/programme-20403/saison-37419/',
'cliches' => 'video/programme-24834/saison-35591/',
'completement' => 'video/programme-23859/saison-34102/',
'fun-facts' => 'video/programme-23040/saison-32686/',
'origin-story' => 'video/programme-25667/saison-37041/'
);
$category = $this->getInput('category');
if(array_key_exists($category, $categories)) {
return static::URI . $categories[$category];
} else {
returnClientError('Emission inconnue');
} }
return $uri;
} }
return parent::getURI(); return parent::getURI();
@@ -85,23 +64,24 @@ class AllocineFRBridge extends BridgeAbstract {
self::PARAMETERS[$this->queriedContext]['category']['values'] self::PARAMETERS[$this->queriedContext]['category']['values']
); );
foreach($html->find('div[class=gd-col-left]', 0)->find('div[class*=video-card]') as $element) { foreach($html->find('.media-meta-list figure.media-meta-fig') as $element) {
$item = array(); $item = array();
$title = $element->find('a[class*=meta-title-link]', 0); $title = $element->find('div.titlebar h3.title a', 0);
$content = trim($element->outertext); $content = trim($element->innertext);
$figCaption = strpos($content, $category);
// Replace image 'src' with the one in 'data-src'
$content = preg_replace('@src="data:image/gif;base64,[A-Za-z0-9+\/]*"@', '', $content);
$content = preg_replace('@data-src=@', 'src=', $content);
// Remove date in the content to prevent content update while the video is getting older
$content = preg_replace('@<div class="meta-sub light">.*<span>[^<]*</span>[^<]*</div>@', '', $content);
if($figCaption !== false) {
$content = str_replace('src="/', 'src="' . static::URI, $content);
$content = str_replace('href="/', 'href="' . static::URI, $content);
$content = str_replace('src=\'/', 'src=\'' . static::URI, $content);
$content = str_replace('href=\'/', 'href=\'' . static::URI, $content);
$item['content'] = $content; $item['content'] = $content;
$item['title'] = trim($title->innertext); $item['title'] = trim($title->innertext);
$item['uri'] = static::URI . substr($title->href, 1); $item['uri'] = static::URI . $title->href;
$this->items[] = $item; $this->items[] = $item;
} }
} }
}
} }

View File

@@ -16,6 +16,7 @@ class AmazonBridge extends BridgeAbstract {
'sort' => array( 'sort' => array(
'name' => 'Sort by', 'name' => 'Sort by',
'type' => 'list', 'type' => 'list',
'required' => false,
'values' => array( 'values' => array(
'Relevance' => 'relevanceblender', 'Relevance' => 'relevanceblender',
'Price: Low to High' => 'price-asc-rank', 'Price: Low to High' => 'price-asc-rank',
@@ -28,6 +29,7 @@ class AmazonBridge extends BridgeAbstract {
'tld' => array( 'tld' => array(
'name' => 'Country', 'name' => 'Country',
'type' => 'list', 'type' => 'list',
'required' => true,
'values' => array( 'values' => array(
'Australia' => 'com.au', 'Australia' => 'com.au',
'Brazil' => 'com.br', 'Brazil' => 'com.br',
@@ -70,9 +72,6 @@ class AmazonBridge extends BridgeAbstract {
// Title // Title
$title = $element->find('h2', 0); $title = $element->find('h2', 0);
if (is_null($title)) {
continue;
}
$item['title'] = html_entity_decode($title->innertext, ENT_QUOTES); $item['title'] = html_entity_decode($title->innertext, ENT_QUOTES);

View File

@@ -19,6 +19,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
'tld' => array( 'tld' => array(
'name' => 'Country', 'name' => 'Country',
'type' => 'list', 'type' => 'list',
'required' => true,
'values' => array( 'values' => array(
'Australia' => 'com.au', 'Australia' => 'com.au',
'Brazil' => 'com.br', 'Brazil' => 'com.br',
@@ -32,7 +33,6 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
'Mexico' => 'com.mx', 'Mexico' => 'com.mx',
'Netherlands' => 'nl', 'Netherlands' => 'nl',
'Spain' => 'es', 'Spain' => 'es',
'Sweden' => 'se',
'United Kingdom' => 'co.uk', 'United Kingdom' => 'co.uk',
'United States' => 'com', 'United States' => 'com',
), ),
@@ -135,11 +135,11 @@ EOT;
// data-asin="B00WTHJ5SU" data-asin-price="14.99" data-asin-shipping="0" // data-asin="B00WTHJ5SU" data-asin-price="14.99" data-asin-shipping="0"
// data-asin-currency-code="USD" data-substitute-count="-1" ... /> // data-asin-currency-code="USD" data-substitute-count="-1" ... />
if ($asinData) { if ($asinData) {
return array( return [
'price' => $asinData->getAttribute('data-asin-price'), 'price' => $asinData->getAttribute('data-asin-price'),
'currency' => $asinData->getAttribute('data-asin-currency-code'), 'currency' => $asinData->getAttribute('data-asin-currency-code'),
'shipping' => $asinData->getAttribute('data-asin-shipping') 'shipping' => $asinData->getAttribute('data-asin-shipping')
); ];
} }
return false; return false;
@@ -151,11 +151,11 @@ EOT;
preg_match('/^\s*([A-Z]{3}|£|\$)\s?([\d.,]+)\s*$/', $priceDiv->plaintext, $matches); preg_match('/^\s*([A-Z]{3}|£|\$)\s?([\d.,]+)\s*$/', $priceDiv->plaintext, $matches);
if (count($matches) === 3) { if (count($matches) === 3) {
return array( return [
'price' => $matches[2], 'price' => $matches[2],
'currency' => $matches[1], 'currency' => $matches[1],
'shipping' => '0' 'shipping' => '0'
); ];
} }
return false; return false;

View File

@@ -3,9 +3,7 @@ class AnidexBridge extends BridgeAbstract {
const MAINTAINER = 'ORelio'; const MAINTAINER = 'ORelio';
const NAME = 'Anidex'; const NAME = 'Anidex';
const URI = 'http://anidex.info/'; // anidex.info has ddos-guard so we need to use anidex.moe const URI = 'https://anidex.info/';
const ALTERNATE_URI = 'https://anidex.moe/'; // anidex.moe returns 301 unless Host is set to anidex.info
const ALTERNATE_HOST = 'anidex.info'; // Correct host for requesting anidex.moe without 301 redirect
const DESCRIPTION = 'Returns the newest torrents, with optional search criteria.'; const DESCRIPTION = 'Returns the newest torrents, with optional search criteria.';
const PARAMETERS = array( const PARAMETERS = array(
array( array(
@@ -110,7 +108,7 @@ class AnidexBridge extends BridgeAbstract {
public function collectData() { public function collectData() {
// Build Search URL from user-provided parameters // Build Search URL from user-provided parameters
$search_url = self::ALTERNATE_URI . '?s=upload_timestamp&o=desc'; $search_url = self::URI . '?s=upload_timestamp&o=desc';
foreach (array('id', 'lang_id', 'group_id') as $param_name) { foreach (array('id', 'lang_id', 'group_id') as $param_name) {
$param = $this->getInput($param_name); $param = $this->getInput($param_name);
if (!empty($param) && intval($param) != 0 && ctype_digit(str_replace(',', '', $param))) { if (!empty($param) && intval($param) != 0 && ctype_digit(str_replace(',', '', $param))) {
@@ -133,16 +131,8 @@ class AnidexBridge extends BridgeAbstract {
$opt[CURLOPT_COOKIE] = 'anidex_h_toggle=' . $h; $opt[CURLOPT_COOKIE] = 'anidex_h_toggle=' . $h;
} }
// We need to use a different Host HTTP header to reach the correct page on ALTERNATE_URI
$headers = array('Host: ' . self::ALTERNATE_HOST);
// The HTTPS certificate presented by anidex.moe is for anidex.info. We need to ignore this.
// As a consequence, the bridge is intentionally marked as insecure by setting self::URI to http://
$opt[CURLOPT_SSL_VERIFYHOST] = 0;
$opt[CURLOPT_SSL_VERIFYPEER] = 0;
// Retrieve torrent listing from search results, which does not contain torrent description // Retrieve torrent listing from search results, which does not contain torrent description
$html = getSimpleHTMLDOM($search_url, $headers, $opt) $html = getSimpleHTMLDOM($search_url, array(), $opt)
or returnServerError('Could not request Anidex: ' . $search_url); or returnServerError('Could not request Anidex: ' . $search_url);
$links = $html->find('a'); $links = $html->find('a');
$results = array(); $results = array();
@@ -166,11 +156,10 @@ class AnidexBridge extends BridgeAbstract {
if ($torrent_id != 0 && ctype_digit($torrent_id)) { if ($torrent_id != 0 && ctype_digit($torrent_id)) {
//Retrieve data for this torrent ID //Retrieve data for this torrent ID
$item_browse_uri = self::URI . 'torrent/' . $torrent_id; $item_uri = self::URI . 'torrent/' . $torrent_id;
$item_fetch_uri = self::ALTERNATE_URI . 'torrent/' . $torrent_id;
//Retrieve full description from torrent page (cached for 24 hours: 86400 seconds) //Retrieve full description from torrent page
if ($item_html = getSimpleHTMLDOMCached($item_fetch_uri, 86400, $headers, $opt)) { if ($item_html = getSimpleHTMLDOMCached($item_uri)) {
//Retrieve data from page contents //Retrieve data from page contents
$item_title = str_replace(' (Torrent) - AniDex ', '', $item_html->find('title', 0)->plaintext); $item_title = str_replace(' (Torrent) - AniDex ', '', $item_html->find('title', 0)->plaintext);
@@ -202,7 +191,7 @@ class AnidexBridge extends BridgeAbstract {
//Build and add final item //Build and add final item
$item = array(); $item = array();
$item['uri'] = $item_browse_uri; $item['uri'] = $item_uri;
$item['title'] = $item_title; $item['title'] = $item_title;
$item['author'] = $item_author; $item['author'] = $item_author;
$item['timestamp'] = $item_date; $item['timestamp'] = $item_date;

View File

@@ -102,6 +102,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
$item_description = defaultLinkTo($item_description, self::URI); $item_description = defaultLinkTo($item_description, self::URI);
$item_description = str_replace("\r", '', $item_description); $item_description = str_replace("\r", '', $item_description);
$item_description = str_replace("\n", '', $item_description); $item_description = str_replace("\n", '', $item_description);
$item_description = utf8_encode($item_description);
//Build and add final item //Build and add final item
$item = array(); $item = array();
@@ -136,4 +137,5 @@ class AnimeUltimeBridge extends BridgeAbstract {
return parent::getName(); return parent::getName();
} }
} }

View File

@@ -1,149 +0,0 @@
<?php
class AppleAppStoreBridge extends BridgeAbstract {
const MAINTAINER = 'captn3m0';
const NAME = 'Apple App Store';
const URI = 'https://apps.apple.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns version updates for a specific application';
const PARAMETERS = array(array(
'id' => array(
'name' => 'Application ID',
'required' => true,
'exampleValue' => '310633997'
),
'p' => array(
'name' => 'Platform',
'type' => 'list',
'values' => array(
'iPad' => 'ipad',
'iPhone' => 'iphone',
'Mac' => 'mac',
// The following 2 are present in responses
// but not yet tested
'Web' => 'web',
'Apple TV' => 'appletv',
),
'defaultValue' => 'iphone',
),
'country' => array(
'name' => 'Store Country',
'type' => 'list',
'values' => array(
'US' => 'US',
'India' => 'IN',
'Canada' => 'CA'
),
'defaultValue' => 'US',
),
));
const PLATFORM_MAPPING = array(
'iphone' => 'ios',
'ipad' => 'ios',
);
private function makeHtmlUrl($id, $country){
return 'https://apps.apple.com/' . $country . '/app/id' . $id;
}
private function makeJsonUrl($id, $platform, $country){
return "https://amp-api.apps.apple.com/v1/catalog/$country/apps/$id?platform=$platform&extend=versionHistory";
}
public function getName(){
if (isset($this->name)) {
return $this->name . ' - AppStore Updates';
}
return parent::getName();
}
/**
* In case of some platforms, the data is present in the initial response
*/
private function getDataFromShoebox($id, $platform, $country){
$uri = $this->makeHtmlUrl($id, $country);
$html = getSimpleHTMLDOMCached($uri, 3600);
$script = $html->find('script[id="shoebox-ember-data-store"]', 0);
$json = json_decode($script->innertext, true);
return $json['data'];
}
private function getJWTToken($id, $platform, $country){
$uri = $this->makeHtmlUrl($id, $country);
$html = getSimpleHTMLDOMCached($uri, 3600);
$meta = $html->find('meta[name="web-experience-app/config/environment"]', 0);
$json = urldecode($meta->content);
$json = json_decode($json);
return $json->MEDIA_API->token;
}
private function getAppData($id, $platform, $country, $token){
$uri = $this->makeJsonUrl($id, $platform, $country);
$headers = array(
"Authorization: Bearer $token",
);
$json = json_decode(getContents($uri, $headers), true);
return $json['data'][0];
}
/**
* Parses the version history from the data received
* @return array list of versions with details on each element
*/
private function getVersionHistory($data, $platform){
switch($platform) {
case 'mac':
return $data['relationships']['platforms']['data'][0]['attributes']['versionHistory'];
default:
$os = self::PLATFORM_MAPPING[$platform];
return $data['attributes']['platformAttributes'][$os]['versionHistory'];
}
}
public function collectData() {
$id = $this->getInput('id');
$country = $this->getInput('country');
$platform = $this->getInput('p');
switch ($platform) {
case 'mac':
$data = $this->getDataFromShoebox($id, $platform, $country);
break;
default:
$token = $this->getJWTToken($id, $platform, $country);
$data = $this->getAppData($id, $platform, $country, $token);
}
$versionHistory = $this->getVersionHistory($data, $platform);
$name = $this->name = $data['attributes']['name'];
$author = $data['attributes']['artistName'];
foreach ($versionHistory as $row) {
$item = array();
$item['content'] = nl2br($row['releaseNotes']);
$item['title'] = $name . ' - ' . $row['versionDisplay'];
$item['timestamp'] = $row['releaseDate'];
$item['author'] = $author;
$item['uri'] = $this->makeHtmlUrl($id, $country);
$this->items[] = $item;
}
}
}

View File

@@ -1,70 +0,0 @@
<?php
class AppleMusicBridge extends BridgeAbstract {
const NAME = 'Apple Music';
const URI = 'https://www.apple.com';
const DESCRIPTION = 'Fetches the latest releases from an artist';
const MAINTAINER = 'Limero';
const PARAMETERS = array(array(
'url' => array(
'name' => 'Artist URL',
'exampleValue' => 'https://itunes.apple.com/us/artist/dunderpatrullen/329796274',
'required' => true,
),
'imgSize' => array(
'name' => 'Image size for thumbnails (in px)',
'type' => 'number',
'defaultValue' => 512,
'required' => true,
)
));
const CACHE_TIMEOUT = 21600; // 6 hours
private $title;
public function collectData() {
$url = $this->getInput('url');
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request: ' . $url);
$imgSize = $this->getInput('imgSize');
$this->title = $html->find('title', 0)->innertext;
// 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[] = array(
'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'] = array(
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'];
});
}
public function getName() {
return $this->title ?: parent::getName();
}
}

View File

@@ -1,93 +0,0 @@
<?php
class ArtStationBridge extends BridgeAbstract {
const NAME = 'ArtStation';
const URI = 'https://www.artstation.com';
const DESCRIPTION = 'Fetches the latest ten artworks from a search query on ArtStation.';
const MAINTAINER = 'thefranke';
const CACHE_TIMEOUT = 3600; // 1h
const PARAMETERS = array(
'Search Query' => array(
'q' => array(
'name' => 'Search term',
'required' => true
)
)
);
public function getIcon() {
return 'https://www.artstation.com/assets/favicon-58653022bc38c1905ac7aa1b10bffa6b.ico';
}
public function getName() {
return self::NAME . ': ' . $this->getInput('q');
}
private function fetchSearch($searchQuery) {
$data = '{"query":"' . $searchQuery . '","page":1,"per_page":50,"sorting":"date",';
$data .= '"pro_first":"1","filters":[],"additional_fields":[]}';
$header = array(
'Content-Type: application/json',
'Accept: application/json'
);
$opts = array(
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_RETURNTRANSFER => true
);
$jsonSearchURL = self::URI . '/api/v2/search/projects.json';
$jsonSearchStr = getContents($jsonSearchURL, $header, $opts)
or returnServerError('Could not fetch JSON for search query.');
return json_decode($jsonSearchStr);
}
private function fetchProject($hashID) {
$jsonProjectURL = self::URI . '/projects/' . $hashID . '.json';
$jsonProjectStr = getContents($jsonProjectURL)
or returnServerError('Could not fetch JSON for project.');
return json_decode($jsonProjectStr);
}
public function collectData() {
$searchTerm = $this->getInput('q');
$jsonQuery = $this->fetchSearch($searchTerm);
foreach($jsonQuery->data as $media) {
// get detailed info about media item
$jsonProject = $this->fetchProject($media->hash_id);
// create item
$item = array();
$item['title'] = $media->title;
$item['uri'] = $media->url;
$item['timestamp'] = strtotime($jsonProject->published_at);
$item['author'] = $media->user->full_name;
$item['categories'] = implode(',', $jsonProject->tags);
$item['content'] = '<a href="'
. $media->url
. '"><img style="max-width: 100%" src="'
. $jsonProject->cover_url
. '"></a><p>'
. $jsonProject->description
. '</p>';
$numAssets = count($jsonProject->assets);
if ($numAssets > 1)
$item['content'] .= '<p><a href="'
. $media->url
. '">Project contains '
. ($numAssets - 1)
. ' more item(s).</a></p>';
$this->items[] = $item;
if (count($this->items) >= 10)
break;
}
}
}

View File

@@ -1,7 +1,7 @@
<?php <?php
class Arte7Bridge extends BridgeAbstract { class Arte7Bridge extends BridgeAbstract {
// const MAINTAINER = 'mitsukarenai'; const MAINTAINER = 'mitsukarenai';
const NAME = 'Arte +7'; const NAME = 'Arte +7';
const URI = 'https://www.arte.tv/'; const URI = 'https://www.arte.tv/';
const CACHE_TIMEOUT = 1800; // 30min const CACHE_TIMEOUT = 1800; // 30min
@@ -91,8 +91,7 @@ class Arte7Bridge extends BridgeAbstract {
'Authorization: Bearer ' . self::API_TOKEN 'Authorization: Bearer ' . self::API_TOKEN
); );
$input = getContents($url, $header) $input = getContents($url, $header) or die('Could not request ARTE.');
or returnServerError('Could not request ARTE.');
$input_json = json_decode($input, true); $input_json = json_decode($input, true);
foreach($input_json['videos'] as $element) { foreach($input_json['videos'] as $element) {
@@ -120,4 +119,5 @@ class Arte7Bridge extends BridgeAbstract {
$this->items[] = $item; $this->items[] = $item;
} }
} }
} }

View File

@@ -1,72 +0,0 @@
<?php
class AsahiShimbunAJWBridge extends BridgeAbstract {
const NAME = 'Asahi Shimbun AJW';
const BASE_URI = 'http://www.asahi.com';
const URI = self::BASE_URI . '/ajw/';
const DESCRIPTION = 'Asahi Shimbun - Asia & Japan Watch';
const MAINTAINER = 'somini';
const PARAMETERS = array(
array(
'section' => 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;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +0,0 @@
<?php
class AtmoOccitanieBridge extends BridgeAbstract {
const NAME = 'Atmo Occitanie';
const URI = 'https://www.atmo-occitanie.org/';
const DESCRIPTION = 'Fetches the latest air polution of cities in Occitanie from Atmo';
const MAINTAINER = 'floviolleau';
const PARAMETERS = array(array(
'city' => array(
'name' => 'Ville',
'required' => true
)
));
const CACHE_TIMEOUT = 7200;
public function collectData() {
$uri = self::URI . $this->getInput('city');
$html = getSimpleHTMLDOM($uri)
or returnServerError('Could not request ' . $uri);
$generalMessage = $html->find('.landing-ville .city-banner .iqa-avertissement', 0)->innertext;
$recommendationsDom = $html->find('.landing-ville .recommandations', 0);
$recommendationsItemDom = $recommendationsDom->find('.recommandation-item .label');
$recommendationsMessage = '';
$i = 0;
$len = count($recommendationsItemDom);
foreach ($recommendationsItemDom as $key => $value) {
if ($i == 0) {
$recommendationsMessage .= trim($value->innertext) . '.';
} else {
$recommendationsMessage .= ' ' . trim($value->innertext) . '.';
}
$i++;
}
$lastRecommendationsDom = $recommendationsDom->find('.col-md-6', -1);
$informationHeaderMessage = $lastRecommendationsDom->find('.heading', 0)->innertext;
$indice = $lastRecommendationsDom->find('.current-indice .indice div', 0)->innertext;
$informationDescriptionMessage = $lastRecommendationsDom->find('.current-indice .description p', 0)->innertext;
$message = "$generalMessage L'indice est de $indice/10. $informationDescriptionMessage. $recommendationsMessage";
$city = $this->getInput('city');
$item['uri'] = $uri;
$today = date('d/m/Y');
$item['title'] = "Bulletin de l'air du $today pour la ville : $city.";
//$item['title'] .= ' Retrouvez plus d\'informations en allant sur atmo-occitanie.org #QualiteAir. ' . $message;
$item['title'] .= ' #QualiteAir. ' . $message;
$item['author'] = 'floviolleau';
$item['content'] = $message;
$item['uid'] = hash('sha256', $item['title']);
$this->items[] = $item;
}
}

View File

@@ -3,143 +3,63 @@
class AutoJMBridge extends BridgeAbstract { class AutoJMBridge extends BridgeAbstract {
const NAME = 'AutoJM'; const NAME = 'AutoJM';
const URI = 'https://www.autojm.fr/'; const URI = 'http://www.autojm.fr/';
const DESCRIPTION = 'Suivre les offres de véhicules proposés par AutoJM en fonction des critères de filtrages'; const DESCRIPTION = 'Suivre les offres de véhicules proposés par AutoJM en fonction des critères de filtrages';
const MAINTAINER = 'sysadminstory'; const MAINTAINER = 'sysadminstory';
const PARAMETERS = array( const PARAMETERS = array(
'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM' => array( 'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM' => array(
'url' => array( 'url' => array(
'name' => 'URL du modèle', 'name' => 'URL de la recherche',
'type' => 'text', 'type' => 'text',
'required' => true, 'required' => true,
'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/', 'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/',
'exampleValue' => 'achat-voitures-neuves-peugeot-nouvelle-308-5p' 'exampleValue' => 'gammes/index/398?order_by=finition_asc&energie[]=3&transmission[]=2&dispo=all'
),
'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; const CACHE_TIMEOUT = 3600;
public function getIcon() { public function getIcon() {
return self::URI . 'favicon.ico'; return self::URI . 'assets/images/favicon.ico';
}
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() { public function collectData() {
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
$model_url = self::URI . $this->getInput('url');
// Build the GET data
$get_data = 'form[energy]=' . $this->getInput('energy') .
'&form[transmission]=' . $this->getInput('transmission') .
'&form[priceMin]=' . $this->getInput('priceMin') .
'&form[priceMin]=' . $this->getInput('priceMin');
// Set the header 'X-Requested-With' like the website does it
$header = array(
'X-Requested-With: XMLHttpRequest'
);
// Get the JSON content of the form
$json = getContents($model_url . '?' . $get_data, $header)
or returnServerError('Could not request AutoJM.'); or returnServerError('Could not request AutoJM.');
$list = $html->find('div[class*=ligne_modele]');
// Extract the HTML content from the JSON result foreach($list as $element) {
$data = json_decode($json); $image = $element->find('img[class=width-100]', 0)->src;
$html = str_get_html($data->results); $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;
// Go through every car of the model if($element->find('div[class*=hasStock-info]', 0) != null) {
$list = $html->find('div[class=car-card]'); $dispo = 'Disponible';
foreach ($list as $car) {
// Get the Finish name if this car is the first of a new finish
$prev_tag = $car->prev_sibling();
if($prev_tag->tag == 'div' && $prev_tag->class == 'results-title') {
$finish_name = $prev_tag->plaintext;
}
// Get the info about the car offer
$image = $car->find('div[class=car-card__visual]', 0)->find('img', 0)->src;
$serie = $car->find('div[class=car-card__title]', 0)->plaintext;
$url = $car->find('a', 0)->href;
// Check if the car model is in stock or available only on order
if($car->find('span[class*=tag--dispo]', 0) != null) {
$availability = 'En Stock';
} else { } else {
$availability = 'Sur commande'; $dispo = 'Sur commande';
} }
$discount_html = $car->find('span[class=promo]', 0); $carburant = str_replace('dispo |', '', $element->find('div[class=carburant]', 0)->plaintext);
// Check if there is any discount dsiplayed $transmission = $element->find('div[class*=bv]', 0)->plaintext;
if ($discount_html != null) { $places = $element->find('div[class*=places]', 0)->plaintext;
$discount = $discount_html->plaintext; $portes = $element->find('div[class*=nb_portes]', 0)->plaintext;
} else { $carosserie = $element->find('div[class*=coloris]', 0)->plaintext;
$discount = 'inconnue'; $remise = $element->find('div[class*=remise]', 0)->plaintext;
} $prix = $element->find('div[class*=prixjm]', 0)->plaintext;
$price = $car->find('span[class=price]', 0)->plaintext;
// Construct the new item
$item = array(); $item = array();
$item['title'] = $finish_name . ' ' . $serie; $item['uri'] = $url;
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />' $item['title'] = $serie;
. $finish_name . ' ' . $serie . '</p>'; $item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />' . $serie . '</p>';
$item['content'] .= '<ul><li>Disponibilité : ' . $availability . '</li>'; $item['content'] .= '<ul><li>Disponibilité : ' . $dispo . '</li>';
$item['content'] .= '<li>Carburant : ' . $carburant . '</li>';
$item['content'] .= '<li>Transmission : ' . $transmission . '</li>';
$item['content'] .= '<li>Nombre de places : ' . $places . '</li>';
$item['content'] .= '<li>Nombre de portes : ' . $portes . '</li>';
$item['content'] .= '<li>Série : ' . $serie . '</li>'; $item['content'] .= '<li>Série : ' . $serie . '</li>';
$item['content'] .= '<li>Remise : ' . $discount . '</li>'; $item['content'] .= '<li>Carosserie : ' . $carosserie . '</li>';
$item['content'] .= '<li>Prix : ' . $price . '</li></ul>'; $item['content'] .= '<li>Remise : ' . $remise . '</li>';
$item['content'] .= '<li>Prix : ' . $prix . '</li></ul>';
// 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; $this->items[] = $item;
} }
} }
} }

View File

@@ -1,55 +0,0 @@
<?php
class AwwwardsBridge extends BridgeAbstract {
const NAME = 'Awwwards';
const URI = 'https://www.awwwards.com/';
const DESCRIPTION = 'Fetches the latest ten sites of the day from Awwwards';
const MAINTAINER = 'Paroleen';
const CACHE_TIMEOUT = 3600;
const SITESURI = 'https://www.awwwards.com/websites/sites_of_the_day/';
const SITEURI = 'https://www.awwwards.com/sites/';
const ASSETSURI = 'https://assets.awwwards.com/awards/media/cache/thumb_417_299/';
private $sites = array();
public function getIcon() {
return 'https://www.awwwards.com/favicon.ico';
}
private function fetchSites() {
Debug::log('Fetching all sites');
$sites = getSimpleHTMLDOM(self::SITESURI)
or returnServerError('Could not fetch JSON for sites.');
Debug::log('Parsing all JSON data');
foreach($sites->find('li[data-model]') as $site) {
$decode = html_entity_decode($site->attr['data-model'],
ENT_QUOTES, 'utf-8');
$decode = json_decode($decode, true);
$this->sites[] = $decode;
}
}
public function collectData() {
$this->fetchSites();
Debug::log('Building RSS feed');
foreach($this->sites as $site) {
$item = array();
$item['title'] = $site['title'];
$item['timestamp'] = $site['createdAt'];
$item['categories'] = $site['tags'];
$item['content'] = '<img src="'
. self::ASSETSURI
. $site['images']['thumbnail']
. '">';
$item['uri'] = self::SITEURI . $site['slug'];
$this->items[] = $item;
if(count($this->items) >= 10)
break;
}
}
}

View File

@@ -55,7 +55,9 @@ class BAEBridge extends BridgeAbstract {
$content .= '<hr>'; $content .= '<hr>';
$content .= $htmlDetail->find('section', 0)->innertext; $content .= $htmlDetail->find('section', 0)->innertext;
$item['content'] = defaultLinkTo($content, parent::getURI()); $content = str_replace('src="/', 'src="' . parent::getURI() . '/', $content);
$content = str_replace('href="/', 'href="' . parent::getURI() . '/', $content);
$item['content'] = $content;
$image = $htmlDetail->find('#zoom', 0); $image = $htmlDetail->find('#zoom', 0);
if ($image) { if ($image) {
$item['enclosures'] = array(parent::getURI() . $image->getAttribute('src')); $item['enclosures'] = array(parent::getURI() . $image->getAttribute('src'));

View File

@@ -1,435 +0,0 @@
<?php
class BadDragonBridge extends BridgeAbstract {
const NAME = 'Bad Dragon Bridge';
const URI = 'https://bad-dragon.com/';
const CACHE_TIMEOUT = 300; // 5min
const DESCRIPTION = 'Returns sales or new clearance items';
const MAINTAINER = 'Roliga';
const PARAMETERS = array(
'Sales' => array(
),
'Clearance' => array(
'ready_made' => array(
'name' => 'Ready Made',
'type' => 'checkbox'
),
'flop' => array(
'name' => 'Flops',
'type' => 'checkbox'
),
'skus' => array(
'name' => 'Products',
'exampleValue' => 'chanceflared, crackers',
'title' => 'Comma separated list of product SKUs'
),
'onesize' => array(
'name' => 'One-Size',
'type' => 'checkbox'
),
'mini' => array(
'name' => 'Mini',
'type' => 'checkbox'
),
'small' => array(
'name' => 'Small',
'type' => 'checkbox'
),
'medium' => array(
'name' => 'Medium',
'type' => 'checkbox'
),
'large' => array(
'name' => 'Large',
'type' => 'checkbox'
),
'extralarge' => array(
'name' => 'Extra Large',
'type' => 'checkbox'
),
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 'all',
'Accessories' => 'accessories',
'Merchandise' => 'merchandise',
'Dildos' => 'insertable',
'Masturbators' => 'penetrable',
'Packers' => 'packer',
'Lil\' Squirts' => 'shooter',
'Lil\' Vibes' => 'vibrator',
'Wearables' => 'wearable'
),
'defaultValue' => 'all',
),
'soft' => array(
'name' => 'Soft Firmness',
'type' => 'checkbox'
),
'med_firm' => array(
'name' => 'Medium Firmness',
'type' => 'checkbox'
),
'firm' => array(
'name' => 'Firm',
'type' => 'checkbox'
),
'split' => array(
'name' => 'Split Firmness',
'type' => 'checkbox'
),
'maxprice' => array(
'name' => 'Max Price',
'type' => 'number',
'required' => true,
'defaultValue' => 300
),
'minprice' => array(
'name' => 'Min Price',
'type' => 'number',
'defaultValue' => 0
),
'cumtube' => array(
'name' => 'Cumtube',
'type' => 'checkbox'
),
'suctionCup' => array(
'name' => 'Suction Cup',
'type' => 'checkbox'
),
'noAccessories' => array(
'name' => 'No Accessories',
'type' => 'checkbox'
)
)
);
/*
* This sets index $strFrom (or $strTo if set) in $outArr to 'on' if
* $inArr[$param] contains $strFrom.
* It is used for translating BD's shop filter URLs into something we can use.
*
* For the query '?type[]=ready_made&type[]=flop' we would have an array like:
* Array (
* [type] => Array (
* [0] => ready_made
* [1] => flop
* )
* )
* which could be translated into:
* Array (
* [ready_made] => on
* [flop] => on
* )
* */
private function setParam($inArr, &$outArr, $param, $strFrom, $strTo = null) {
if(isset($inArr[$param]) && in_array($strFrom, $inArr[$param])) {
$outArr[($strTo ?: $strFrom)] = 'on';
}
}
public function detectParameters($url) {
$params = array();
// Sale
$regex = '/^(https?:\/\/)?bad-dragon\.com\/sales/';
if(preg_match($regex, $url, $matches) > 0) {
return $params;
}
// Clearance
$regex = '/^(https?:\/\/)?bad-dragon\.com\/shop\/clearance/';
if(preg_match($regex, $url, $matches) > 0) {
parse_str(parse_url($url, PHP_URL_QUERY), $urlParams);
$this->setParam($urlParams, $params, 'type', 'ready_made');
$this->setParam($urlParams, $params, 'type', 'flop');
if(isset($urlParams['skus'])) {
$skus = array();
foreach($urlParams['skus'] as $sku) {
is_string($sku) && $skus[] = $sku;
is_array($sku) && $skus[] = $sku[0];
}
$params['skus'] = implode(',', $skus);
}
$this->setParam($urlParams, $params, 'sizes', 'onesize');
$this->setParam($urlParams, $params, 'sizes', 'mini');
$this->setParam($urlParams, $params, 'sizes', 'small');
$this->setParam($urlParams, $params, 'sizes', 'medium');
$this->setParam($urlParams, $params, 'sizes', 'large');
$this->setParam($urlParams, $params, 'sizes', 'extralarge');
if(isset($urlParams['category'])) {
$params['category'] = strtolower($urlParams['category']);
} else{
$params['category'] = 'all';
}
$this->setParam($urlParams, $params, 'firmnessValues', 'soft');
$this->setParam($urlParams, $params, 'firmnessValues', 'medium', 'med_firm');
$this->setParam($urlParams, $params, 'firmnessValues', 'firm');
$this->setParam($urlParams, $params, 'firmnessValues', 'split');
if(isset($urlParams['price'])) {
isset($urlParams['price']['max'])
&& $params['maxprice'] = $urlParams['price']['max'];
isset($urlParams['price']['min'])
&& $params['minprice'] = $urlParams['price']['min'];
}
isset($urlParams['cumtube'])
&& $urlParams['cumtube'] === '1'
&& $params['cumtube'] = 'on';
isset($urlParams['suctionCup'])
&& $urlParams['suctionCup'] === '1'
&& $params['suctionCup'] = 'on';
isset($urlParams['noAccessories'])
&& $urlParams['noAccessories'] === '1'
&& $params['noAccessories'] = 'on';
return $params;
}
return null;
}
public function getName() {
switch($this->queriedContext) {
case 'Sales':
return 'Bad Dragon Sales';
case 'Clearance':
return 'Bad Dragon Clearance Search';
default:
return parent::getName();
}
}
public function getURI() {
switch($this->queriedContext) {
case 'Sales':
return self::URI . 'sales';
case 'Clearance':
return $this->inputToURL();
default:
return parent::getURI();
}
}
public function collectData() {
switch($this->queriedContext) {
case 'Sales':
$sales = json_decode(getContents(self::URI . 'api/sales'))
or returnServerError('Failed to query BD API');
foreach($sales as $sale) {
$item = array();
$item['title'] = $sale->title;
$item['timestamp'] = strtotime($sale->startDate);
$item['uri'] = $this->getURI() . '/' . $sale->slug;
$contentHTML = '<p><img src="' . $sale->image->url . '"></p>';
if(isset($sale->endDate)) {
$contentHTML .= '<p><b>This promotion ends on '
. gmdate('M j, Y \a\t g:i A T', strtotime($sale->endDate))
. '</b></p>';
} else {
$contentHTML .= '<p><b>This promotion never ends</b></p>';
}
$ul = false;
$content = json_decode($sale->content);
foreach($content->blocks as $block) {
switch($block->type) {
case 'header-one':
$contentHTML .= '<h1>' . $block->text . '</h1>';
break;
case 'header-two':
$contentHTML .= '<h2>' . $block->text . '</h2>';
break;
case 'header-three':
$contentHTML .= '<h3>' . $block->text . '</h3>';
break;
case 'unordered-list-item':
if(!$ul) {
$contentHTML .= '<ul>';
$ul = true;
}
$contentHTML .= '<li>' . $block->text . '</li>';
break;
default:
if($ul) {
$contentHTML .= '</ul>';
$ul = false;
}
$contentHTML .= '<p>' . $block->text . '</p>';
break;
}
}
$item['content'] = $contentHTML;
$this->items[] = $item;
}
break;
case 'Clearance':
$toyData = json_decode(getContents($this->inputToURL(true)))
or returnServerError('Failed to query BD API');
$productList = json_decode(getContents(self::URI
. 'api/inventory-toy/product-list'))
or returnServerError('Failed to query BD API');
foreach($toyData->toys as $toy) {
$item = array();
$item['uri'] = $this->getURI()
. '#'
. $toy->id;
$item['timestamp'] = strtotime($toy->created);
foreach($productList as $product) {
if($product->sku == $toy->sku) {
$item['title'] = $product->name;
break;
}
}
// images
$content = '<p>';
foreach($toy->images as $image) {
$content .= '<a href="'
. $image->fullFilename
. '"><img src="'
. $image->thumbFilename
. '" /></a>';
}
// price
$content .= '</p><p><b>Price:</b> $'
. $toy->price
// size
. '<br /><b>Size:</b> '
. $toy->size
// color
. '<br /><b>Color:</b> '
. $toy->color
// features
. '<br /><b>Features:</b> '
. ($toy->suction_cup ? 'Suction cup' : '')
. ($toy->suction_cup && $toy->cumtube ? ', ' : '')
. ($toy->cumtube ? 'Cumtube' : '')
. ($toy->suction_cup || $toy->cumtube ? '' : 'None');
// firmness
$firmnessTexts = array(
'2' => 'Extra soft',
'3' => 'Soft',
'5' => 'Medium',
'8' => 'Firm'
);
$firmnesses = explode('/', $toy->firmness);
if(count($firmnesses) === 2) {
$content .= '<br /><b>Firmness:</b> '
. $firmnessTexts[$firmnesses[0]]
. ', '
. $firmnessTexts[$firmnesses[1]];
} else{
$content .= '<br /><b>Firmness:</b> '
. $firmnessTexts[$firmnesses[0]];
}
// flop
if($toy->type === 'flop') {
$content .= '<br /><b>Flop reason:</b> '
. $toy->flop_reason;
}
$content .= '</p>';
$item['content'] = $content;
$enclosures = array();
foreach($toy->images as $image) {
$enclosures[] = $image->fullFilename;
}
$item['enclosures'] = $enclosures;
$categories = array();
$categories[] = $toy->sku;
$categories[] = $toy->type;
$categories[] = $toy->size;
if($toy->cumtube) {
$categories[] = 'cumtube';
}
if($toy->suction_cup) {
$categories[] = 'suction_cup';
}
$item['categories'] = $categories;
$this->items[] = $item;
}
break;
}
}
private function inputToURL($api = false) {
$url = self::URI;
$url .= ($api ? 'api/inventory-toys?' : 'shop/clearance?');
// Default parameters
$url .= 'limit=60';
$url .= '&page=1';
$url .= '&sort[field]=created';
$url .= '&sort[direction]=desc';
// Product types
$url .= ($this->getInput('ready_made') ? '&type[]=ready_made' : '');
$url .= ($this->getInput('flop') ? '&type[]=flop' : '');
// Product names
foreach(array_filter(explode(',', $this->getInput('skus'))) as $sku) {
$url .= '&skus[]=' . urlencode(trim($sku));
}
// Size
$url .= ($this->getInput('onesize') ? '&sizes[]=onesize' : '');
$url .= ($this->getInput('mini') ? '&sizes[]=mini' : '');
$url .= ($this->getInput('small') ? '&sizes[]=small' : '');
$url .= ($this->getInput('medium') ? '&sizes[]=medium' : '');
$url .= ($this->getInput('large') ? '&sizes[]=large' : '');
$url .= ($this->getInput('extralarge') ? '&sizes[]=extralarge' : '');
// Category
$url .= ($this->getInput('category') ? '&category='
. urlencode($this->getInput('category')) : '');
// Firmness
if($api) {
$url .= ($this->getInput('soft') ? '&firmnessValues[]=3' : '');
$url .= ($this->getInput('med_firm') ? '&firmnessValues[]=5' : '');
$url .= ($this->getInput('firm') ? '&firmnessValues[]=8' : '');
if($this->getInput('split')) {
$url .= '&firmnessValues[]=3/5';
$url .= '&firmnessValues[]=3/8';
$url .= '&firmnessValues[]=8/3';
$url .= '&firmnessValues[]=5/8';
$url .= '&firmnessValues[]=8/5';
}
} else{
$url .= ($this->getInput('soft') ? '&firmnessValues[]=soft' : '');
$url .= ($this->getInput('med_firm') ? '&firmnessValues[]=medium' : '');
$url .= ($this->getInput('firm') ? '&firmnessValues[]=firm' : '');
$url .= ($this->getInput('split') ? '&firmnessValues[]=split' : '');
}
// Price
$url .= ($this->getInput('maxprice') ? '&price[max]='
. $this->getInput('maxprice') : '&price[max]=300');
$url .= ($this->getInput('minprice') ? '&price[min]='
. $this->getInput('minprice') : '&price[min]=0');
// Features
$url .= ($this->getInput('cumtube') ? '&cumtube=1' : '');
$url .= ($this->getInput('suctionCup') ? '&suctionCup=1' : '');
$url .= ($this->getInput('noAccessories') ? '&noAccessories=1' : '');
return $url;
}
}

View File

@@ -1,103 +0,0 @@
<?php
class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
const NAME = 'Baka Updates Manga Releases';
const URI = 'https://www.mangaupdates.com/';
const DESCRIPTION = 'Get the latest series releases';
const MAINTAINER = 'fulmeek';
const PARAMETERS = array(array(
'series_id' => array(
'name' => 'Series ID',
'type' => 'number',
'required' => true,
'exampleValue' => '12345'
)
));
const LIMIT_COLS = 5;
const LIMIT_ITEMS = 10;
private $feedName = '';
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Series not found');
// 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');
$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();
$item['content'] = '';
$objDate = $cols[0];
if ($objDate)
$item['timestamp'] = strtotime($objDate->plaintext);
$objTitle = $cols[1];
if ($objTitle) {
$title[] = $this->filterHTML($objTitle->plaintext);
$item['content'] .= '<p>Series: ' . $this->filterText($objTitle->innertext) . '</p>';
}
$objVolume = $cols[2];
if ($objVolume && !empty($objVolume->plaintext))
$title[] = 'Vol.' . $objVolume->plaintext;
$objChapter = $cols[3];
if ($objChapter && !empty($objChapter->plaintext))
$title[] = 'Chp.' . $objChapter->plaintext;
$objAuthor = $cols[4];
if ($objAuthor && !empty($objAuthor->plaintext)) {
$item['author'] = $this->filterHTML($objAuthor->plaintext);
$item['content'] .= '<p>Groups: ' . $this->filterText($objAuthor->innertext) . '</p>';
}
$item['title'] = implode(' ', $title);
$item['uri'] = $this->getURI();
$item['uid'] = $this->getSanitizedHash($item['title']);
$this->items[] = $item;
}
}
public function getURI(){
$series_id = $this->getInput('series_id');
if (!empty($series_id)) {
return self::URI . 'releases.html?search=' . $series_id . '&stype=series';
}
return self::URI;
}
public function getName(){
if(!empty($this->feedName)) {
return $this->feedName . ' - ' . self::NAME;
}
return parent::getName();
}
private function getSanitizedHash($string) {
return hash('sha1', preg_replace('/[^a-zA-Z0-9\-\.]/', '', ucwords(strtolower($string))));
}
private function filterText($text) {
return rtrim($text, '* ');
}
private function filterHTML($text) {
return $this->filterText(html_entity_decode($text));
}
}

View File

@@ -1,365 +1,67 @@
<?php <?php
class BandcampBridge extends BridgeAbstract { class BandcampBridge extends BridgeAbstract {
const MAINTAINER = 'sebsauvage, Roliga'; const MAINTAINER = 'sebsauvage';
const NAME = 'Bandcamp Bridge'; const NAME = 'Bandcamp Tag';
const URI = 'https://bandcamp.com/'; const URI = 'https://bandcamp.com/';
const CACHE_TIMEOUT = 600; // 10min const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = 'New bandcamp releases by tag, band or album'; const DESCRIPTION = 'New bandcamp release by tag';
const PARAMETERS = array( const PARAMETERS = array( array(
'By tag' => array(
'tag' => array( 'tag' => array(
'name' => 'tag', 'name' => 'tag',
'type' => 'text', 'type' => 'text',
'required' => true 'required' => true
) )
), ));
'By band' => array(
'band' => array(
'name' => 'band',
'type' => 'text',
'title' => 'Band name as seen in the band page URL',
'required' => true
),
'type' => array(
'name' => 'Articles are',
'type' => 'list',
'values' => array(
'Releases' => 'releases',
'Releases, new one when track list changes' => 'changes',
'Individual tracks' => 'tracks'
),
'defaultValue' => 'changes'
),
'limit' => array(
'name' => 'limit',
'type' => 'number',
'title' => 'Number of releases to return',
'defaultValue' => 5
)
),
'By album' => array(
'band' => array(
'name' => 'band',
'type' => 'text',
'title' => 'Band name as seen in the album page URL',
'required' => true
),
'album' => array(
'name' => 'album',
'type' => 'text',
'title' => 'Album name as seen in the album page URL',
'required' => true
),
'type' => array(
'name' => 'Articles are',
'type' => 'list',
'values' => array(
'Releases' => 'releases',
'Releases, new one when track list changes' => 'changes',
'Individual tracks' => 'tracks'
),
'defaultValue' => 'tracks'
)
)
);
const IMGURI = 'https://f4.bcbits.com/';
const IMGSIZE_300PX = 23;
const IMGSIZE_700PX = 16;
private $feedName;
public function getIcon() { public function getIcon() {
return 'https://s4.bcbits.com/img/bc_favicon.ico'; return 'https://s4.bcbits.com/img/bc_favicon.ico';
} }
public function collectData(){ public function collectData(){
switch($this->queriedContext) { $html = getSimpleHTMLDOM($this->getURI())
case 'By tag': 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);
$json = json_decode($content); foreach($html->find('li.item') as $release) {
$script = $release->find('div.art', 0)->getAttribute('onclick');
$uri = ltrim($script, "return 'url(");
$uri = rtrim($uri, "')");
if ($json->ok !== true) { $item = array();
returnServerError('Invalid response'); $item['author'] = $release->find('div.itemsubtext', 0)->plaintext
} . ' - '
. $release->find('div.itemtext', 0)->plaintext;
foreach ($json->items as $entry) { $item['title'] = $release->find('div.itemsubtext', 0)->plaintext
$url = $entry->tralbum_url; . ' - '
$artist = $entry->artist; . $release->find('div.itemtext', 0)->plaintext;
$title = $entry->title;
// e.g. record label is the releaser, but not the artist
$releaser = $entry->band_name !== $entry->artist ? $entry->band_name : null;
$full_title = $artist . ' - ' . $title; $item['content'] = '<img src="'
$full_artist = $artist; . $uri
if (isset($releaser)) { . '"/><br/>'
$full_title .= ' (' . $releaser . ')'; . $release->find('div.itemsubtext', 0)->plaintext
$full_artist .= ' (' . $releaser . ')'; . ' - '
} . $release->find('div.itemtext', 0)->plaintext;
$small_img = $this->getImageUrl($entry->art_id, self::IMGSIZE_300PX);
$img = $this->getImageUrl($entry->art_id, self::IMGSIZE_700PX);
$item = array( $item['id'] = $release->find('a', 0)->getAttribute('href');
'uri' => $url, $item['uri'] = $release->find('a', 0)->getAttribute('href');
'author' => $full_artist,
'title' => $full_title
);
$item['content'] = "<img src='$small_img' /><br/>$full_title";
$item['enclosures'] = array($img);
$this->items[] = $item; $this->items[] = $item;
} }
break;
case 'By band':
case 'By album':
$html = getSimpleHTMLDOMCached($this->getURI(), 86400);
if ($html->find('meta[name=title]', 0)) {
$this->feedName = $html->find('meta[name=title]', 0)->content;
} else {
$this->feedName = str_replace('Music | ', '', $html->find('title', 0)->plaintext);
}
$regex = '/band_id=(\d+)/';
if(preg_match($regex, $html, $matches) == false)
returnServerError('Unable to find band ID on: ' . $this->getURI());
$band_id = $matches[1];
$tralbums = array();
switch($this->queriedContext) {
case 'By band':
$query_data = array(
'band_id' => $band_id
);
$band_data = $this->apiGet('mobile/22/band_details', $query_data);
$num_albums = min(count($band_data->discography), $this->getInput('limit'));
for($i = 0; $i < $num_albums; $i++) {
$album_basic_data = $band_data->discography[$i];
// 'a' or 't' for albums and individual tracks respectively
$tralbum_type = substr($album_basic_data->item_type, 0, 1);
$query_data = array(
'band_id' => $band_id,
'tralbum_type' => $tralbum_type,
'tralbum_id' => $album_basic_data->item_id
);
$tralbums[] = $this->apiGet('mobile/22/tralbum_details', $query_data);
}
break;
case 'By album':
$regex = '/album=(\d+)/';
if(preg_match($regex, $html, $matches) == false)
returnServerError('Unable to find album ID on: ' . $this->getURI());
$album_id = $matches[1];
$query_data = array(
'band_id' => $band_id,
'tralbum_type' => 'a',
'tralbum_id' => $album_id
);
$tralbums[] = $this->apiGet('mobile/22/tralbum_details', $query_data);
break;
}
foreach ($tralbums as $tralbum_data) {
if ($tralbum_data->type === 'a' && $this->getInput('type') === 'tracks') {
foreach ($tralbum_data->tracks as $track) {
$query_data = array(
'band_id' => $band_id,
'tralbum_type' => 't',
'tralbum_id' => $track->track_id
);
$track_data = $this->apiGet('mobile/22/tralbum_details', $query_data);
$this->items[] = $this->buildTralbumItem($track_data);
}
} else {
$this->items[] = $this->buildTralbumItem($tralbum_data);
}
}
break;
}
}
private function buildTralbumItem($tralbum_data){
$band_data = $tralbum_data->band;
// Format title like: ARTIST - ALBUM/TRACK (OPTIONAL RELEASER)
// Format artist/author like: ARTIST (OPTIONAL RELEASER)
//
// If the album/track is released under a label/a band other than the artist
// themselves, append that releaser name to the title and artist/author.
//
// This sadly doesn't always work right for individual tracks as the artist
// of the track is always set to the releaser.
$artist = $tralbum_data->tralbum_artist;
$full_title = $artist . ' - ' . $tralbum_data->title;
$full_artist = $artist;
if (isset($tralbum_data->label)) {
$full_title .= ' (' . $tralbum_data->label . ')';
$full_artist .= ' (' . $tralbum_data->label . ')';
} elseif ($band_data->name !== $artist) {
$full_title .= ' (' . $band_data->name . ')';
$full_artist .= ' (' . $band_data->name . ')';
}
$small_img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_300PX);
$img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_700PX);
$item = array(
'uri' => $tralbum_data->bandcamp_url,
'author' => $full_artist,
'title' => $full_title,
'enclosures' => array($img),
'timestamp' => $tralbum_data->release_date
);
$item['categories'] = array();
foreach ($tralbum_data->tags as $tag) {
$item['categories'][] = $tag->norm_name;
}
// Give articles a unique UID depending on its track list
// Releases should then show up as new articles when tracks are added
if ($this->getInput('type') === 'changes') {
$item['uid'] = "bandcamp/$band_data->band_id/$tralbum_data->id/";
foreach ($tralbum_data->tracks as $track) {
$item['uid'] .= $track->track_id;
}
}
$item['content'] = "<img src='$small_img' /><br/>$full_title<br/>";
if ($tralbum_data->type === 'a') {
$item['content'] .= '<ol>';
foreach ($tralbum_data->tracks as $track) {
$item['content'] .= "<li>$track->title</li>";
}
$item['content'] .= '</ol>';
}
if (!empty($tralbum_data->about)) {
$item['content'] .= '<p>'
. nl2br($tralbum_data->about)
. '</p>';
}
return $item;
}
private function buildRequestJson(){
$requestJson = array(
'tag' => $this->getInput('tag'),
'page' => 1,
'sort' => 'date'
);
return json_encode($requestJson);
}
private function getImageUrl($id, $size){
return self::IMGURI . 'img/a' . $id . '_' . $size . '.jpg';
}
private function apiGet($endpoint, $query_data) {
$url = self::URI . 'api/' . $endpoint . '?' . http_build_query($query_data);
$data = json_decode(getContents($url))
or returnServerError('API request to "' . $url . '" failed.');
return $data;
} }
public function getURI(){ public function getURI(){
switch($this->queriedContext) {
case 'By tag':
if(!is_null($this->getInput('tag'))) { if(!is_null($this->getInput('tag'))) {
return self::URI return self::URI . 'tag/' . urlencode($this->getInput('tag')) . '?sort_field=date';
. 'tag/'
. urlencode($this->getInput('tag'))
. '?sort_field=date';
}
break;
case 'By band':
if(!is_null($this->getInput('band'))) {
return 'https://'
. $this->getInput('band')
. '.bandcamp.com/music';
}
break;
case 'By album':
if(!is_null($this->getInput('band')) && !is_null($this->getInput('album'))) {
return 'https://'
. $this->getInput('band')
. '.bandcamp.com/album/'
. $this->getInput('album');
}
break;
} }
return parent::getURI(); return parent::getURI();
} }
public function getName(){ public function getName(){
switch($this->queriedContext) {
case 'By tag':
if(!is_null($this->getInput('tag'))) { if(!is_null($this->getInput('tag'))) {
return $this->getInput('tag') . ' - Bandcamp Tag'; return $this->getInput('tag') . ' - Bandcamp Tag';
} }
break;
case 'By band':
if(isset($this->feedName)) {
return $this->feedName . ' - Bandcamp Band';
} elseif(!is_null($this->getInput('band'))) {
return $this->getInput('band') . ' - Bandcamp Band';
}
break;
case 'By album':
if(isset($this->feedName)) {
return $this->feedName . ' - Bandcamp Album';
} elseif(!is_null($this->getInput('album'))) {
return $this->getInput('album') . ' - Bandcamp Album';
}
break;
}
return parent::getName(); return parent::getName();
} }
public function detectParameters($url) {
$params = array();
// By tag
$regex = '/^(https?:\/\/)?bandcamp\.com\/tag\/([^\/.&?\n]+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['tag'] = urldecode($matches[2]);
return $params;
}
// By band
$regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com/';
if(preg_match($regex, $url, $matches) > 0) {
$params['band'] = urldecode($matches[2]);
return $params;
}
// By album
$regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com\/album\/([^\/.&?\n]+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['band'] = urldecode($matches[2]);
$params['album'] = urldecode($matches[3]);
return $params;
}
return null;
}
} }

View File

@@ -3,11 +3,17 @@ class BastaBridge extends BridgeAbstract {
const MAINTAINER = 'qwertygc'; const MAINTAINER = 'qwertygc';
const NAME = 'Bastamag Bridge'; const NAME = 'Bastamag Bridge';
const URI = 'https://www.bastamag.net/'; const URI = 'http://www.bastamag.net/';
const CACHE_TIMEOUT = 7200; // 2h const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Returns the newest articles.'; const DESCRIPTION = 'Returns the newest articles.';
public function collectData(){ public function collectData(){
// Replaces all relative image URLs by absolute URLs.
// Relative URLs always start with 'local/'!
function replaceImageUrl($content){
return preg_replace('/src=["\']{1}([^"\']+)/ims', 'src=\'' . self::URI . '$1\'', $content);
}
$html = getSimpleHTMLDOM(self::URI . 'spip.php?page=backend') $html = getSimpleHTMLDOM(self::URI . 'spip.php?page=backend')
or returnServerError('Could not request Bastamag.'); or returnServerError('Could not request Bastamag.');
@@ -19,11 +25,7 @@ class BastaBridge extends BridgeAbstract {
$item['title'] = $element->find('title', 0)->innertext; $item['title'] = $element->find('title', 0)->innertext;
$item['uri'] = $element->find('guid', 0)->plaintext; $item['uri'] = $element->find('guid', 0)->plaintext;
$item['timestamp'] = strtotime($element->find('dc:date', 0)->plaintext); $item['timestamp'] = strtotime($element->find('dc:date', 0)->plaintext);
$item['content'] = replaceImageUrl(getSimpleHTMLDOM($item['uri'])->find('div.texte', 0)->innertext);
$html = getSimpleHTMLDOM($item['uri']);
$html = defaultLinkTo($html, self::URI);
$item['content'] = $html->find('div.texte', 0)->innertext;
$this->items[] = $item; $this->items[] = $item;
$limit++; $limit++;
} }

View File

@@ -1,103 +0,0 @@
<?php
class BinanceBridge extends BridgeAbstract {
const NAME = 'Binance';
const URI = 'https://www.binance.com';
const DESCRIPTION = 'Subscribe to the Binance blog or the Binance Zendesk announcements.';
const MAINTAINER = 'thefranke';
const CACHE_TIMEOUT = 3600; // 1h
const PARAMETERS = array( array(
'category' => 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();
}
}

View File

@@ -1,119 +0,0 @@
<?php
class BingSearchBridge extends BridgeAbstract
{
const NAME = 'Bing search';
const URI = 'https://www.bing.com/';
const DESCRIPTION = 'Return images from bing search discover';
const MAINTAINER = 'DnAp';
const PARAMETERS = array(
'Image Discover' => 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 = array();
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'] = '<a href="' . $data['imgurl'] . '">
<img src="' . $data[$sizeKey] . '" alt="' . $item['title'] . '"></a>
<p>Source: <a href="' . $this->curUrl($data['surl']) . '"> </a></p>';
$item['enclosures'] = $data['imgurl'];
$items[] = $item;
}
return $items;
}
private function curUrl($url)
{
if (strlen($url) <= 80) {
return $url;
}
return substr($url, 0, 80) . '...';
}
}

View File

@@ -43,4 +43,5 @@ class BlaguesDeMerdeBridge extends BridgeAbstract {
} }
} }
} }

View File

@@ -1,29 +0,0 @@
<?php
class BleepingComputerBridge extends FeedExpander {
const MAINTAINER = 'csisoap';
const NAME = 'Bleeping Computer';
const URI = 'https://www.bleepingcomputer.com/';
const DESCRIPTION = 'Returns the newest articles.';
protected function parseItem($item){
$item = parent::parseItem($item);
$article_html = getSimpleHTMLDOMCached($item['uri']);
if(!$article_html) {
$item['content'] .= '<p><em>Could not request ' . $this->getName() . ': ' . $item['uri'] . '</em></p>';
return $item;
}
$article_content = $article_html->find('div.articleBody', 0)->innertext;
$article_content = stripRecursiveHTMLSection($article_content, 'div', '<div class="cz-related-article-wrapp');
$item['content'] = trim($article_content);
return $item;
}
public function collectData(){
$feed = static::URI . 'feed/';
$this->collectExpandableDatas($feed);
}
}

View File

@@ -1,60 +0,0 @@
<?php
class BlizzardNewsBridge extends XPathAbstract {
const NAME = 'Blizzard News';
const URI = 'https://news.blizzard.com';
const DESCRIPTION = 'Blizzard (game company) newsfeed';
const MAINTAINER = 'Niehztog';
const PARAMETERS = array(
'' => array(
'locale' => array(
'name' => 'Language',
'type' => 'list',
'values' => array(
'Deutsch' => 'de-de',
'English (EU)' => 'en-gb',
'English (US)' => 'en-us',
'Español (EU)' => 'es-es',
'Español (AL)' => 'es-mx',
'Français' => 'fr-fr',
'Italiano' => 'it-it',
'日本語' => 'ja-jp',
'한국어' => 'ko-kr',
'Polski' => 'pl-pl',
'Português (AL)' => 'pt-br',
'Русский' => 'ru-ru',
'ภาษาไทย' => 'th-th',
'简体中文' => 'zh-cn',
'繁體中文' => 'zh-tw'
),
'defaultValue' => 'en-us',
'title' => 'Select your language'
)
)
);
const CACHE_TIMEOUT = 3600;
const XPATH_EXPRESSION_ITEM = '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article';
const XPATH_EXPRESSION_ITEM_TITLE = './/div/div[2]/h2';
const XPATH_EXPRESSION_ITEM_CONTENT = './/div[@class="ArticleListItem-description"]/div[@class="h6"]';
const XPATH_EXPRESSION_ITEM_URI = './/a[@class="ArticleLink ArticleLink"]/@href';
const XPATH_EXPRESSION_ITEM_AUTHOR = '';
const XPATH_EXPRESSION_ITEM_TIMESTAMP = './/time[@class="ArticleListItem-footerTimestamp"]/@timestamp';
const XPATH_EXPRESSION_ITEM_ENCLOSURES = './/div[@class="ArticleListItem-image"]/@style';
const XPATH_EXPRESSION_ITEM_CATEGORIES = './/div[@class="ArticleListItem-label"]';
const SETTING_FIX_ENCODING = true;
/**
* Source Web page URL (should provide either HTML or XML content)
* @return string
*/
protected function getSourceUrl(){
$locale = $this->getInput('locale');
if('zh-cn' === $locale) {
return 'https://cn.news.blizzard.com';
}
return 'https://news.blizzard.com/' . $locale;
}
}

View File

@@ -0,0 +1,69 @@
<?php
class BloombergBridge extends BridgeAbstract
{
const NAME = 'Bloomberg';
const URI = 'https://www.bloomberg.com/';
const DESCRIPTION = 'Trending stories from Bloomberg';
const MAINTAINER = 'mdemoss';
const PARAMETERS = array(
'Trending Stories' => array(),
'From Search' => array(
'q' => array(
'name' => 'Keyword',
'required' => true
)
)
);
public function getName()
{
switch($this->queriedContext) {
case 'Trending Stories':
return self::NAME . ' Trending Stories';
case 'From Search':
if (!is_null($this->getInput('q'))) {
return self::NAME . ' Search : ' . $this->getInput('q');
}
break;
}
return parent::getName();
}
public function getIcon() {
return 'https://assets.bwbx.io/s3/javelin/public/hub/images/favicon-black-63fe5249d3.png';
}
public function collectData()
{
switch($this->queriedContext) {
case 'Trending Stories': // Get list of top new <article>s from the front page.
$html = getSimpleHTMLDOMCached($this->getURI(), 300);
$stories = $html->find('ul.top-news-v3__stories article.top-news-v3-story');
break;
case 'From Search': // Get list of <article> elements from search.
$html = getSimpleHTMLDOMCached(
$this->getURI() .
'search?sort=time:desc&page=1&query=' .
urlencode($this->getInput('q')), 300
);
$stories = $html->find('div.search-result-items article.search-result-story');
break;
}
foreach ($stories as $element) {
$item['uri'] = $element->find('h1 a', 0)->href;
if (preg_match('#^https://#i', $item['uri']) !== 1) {
$item['uri'] = $this->getURI() . $item['uri'];
}
$articleHtml = getSimpleHTMLDOMCached($item['uri']);
if (!$articleHtml) {
continue;
}
$item['title'] = $element->find('h1 a', 0)->plaintext;
$item['timestamp'] = strtotime($articleHtml->find('meta[name=iso-8601-publish-date],meta[name=date]', 0)->content);
$item['content'] = $articleHtml->find('meta[name=description]', 0)->content;
$this->items[] = $item;
}
}
}

View File

@@ -1,159 +0,0 @@
<?php
class BrutBridge extends BridgeAbstract {
const NAME = 'Brut Bridge';
const URI = 'https://www.brut.media';
const DESCRIPTION = 'Returns 5 newest videos by category and edition';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(array(
'category' => 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',
'Health' => 'health',
),
'defaultValue' => 'news',
),
'edition' => array(
'name' => ' Edition',
'type' => 'list',
'values' => array(
'United States' => 'us',
'United Kingdom' => 'uk',
'France' => 'fr',
'Spain' => 'es',
'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();
}
public function getName() {
if (!is_null($this->getInput('edition')) && !is_null($this->getInput('category'))) {
$parameters = $this->getParameters();
$editionValues = array_flip($parameters[0]['edition']['values']);
$categoryValues = array_flip($parameters[0]['category']['values']);
return $categoryValues[$this->getInput('category')] . ' - ' .
$editionValues[$this->getInput('edition')] . ' - Brut.';
}
return parent::getName();
}
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 = '<video controls poster="' . $this->videoImage . '" preload="none">
<source src="https://content.brut.media/video/' . $this->videoId . '-' . $this->videoType . '-web.mp4"
type="video/mp4">
</video>';
$content .= '<p>' . $description->find('h2.mb-1', 0)->innertext . '</p>';
if ($description->find('div.text.pb-3', 0)->children(1)->class != 'date') {
$content .= '<p>' . $description->find('div.text.pb-3', 0)->children(1)->innertext . '</p>';
}
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);
}
}

View File

@@ -1,219 +0,0 @@
<?php
class BukowskisBridge extends BridgeAbstract
{
const NAME = 'Bukowskis';
const URI = 'https://www.bukowskis.com';
const DESCRIPTION = 'Fetches info about auction objects from Bukowskis auction house';
const MAINTAINER = 'Qluxzz';
const PARAMETERS = array(array(
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All categories' => '',
'Art' => array(
'All' => 'art',
'Classic Art' => 'art.classic-art',
'Classic Finnish Art' => 'art.classic-finnish-art',
'Classic Swedish Art' => 'art.classic-swedish-art',
'Contemporary' => 'art.contemporary',
'Modern Finnish Art' => 'art.modern-finnish-art',
'Modern International Art' => 'art.modern-international-art',
'Modern Swedish Art' => 'art.modern-swedish-art',
'Old Masters' => 'art.old-masters',
'Other' => 'art.other',
'Photographs' => 'art.photographs',
'Prints' => 'art.prints',
'Sculpture' => 'art.sculpture',
'Swedish Old Masters' => 'art.swedish-old-masters',
),
'Asian Ceramics & Works of Art' => array(
'All' => 'asian-ceramics-works-of-art',
'Other' => 'asian-ceramics-works-of-art.other',
'Porcelain' => 'asian-ceramics-works-of-art.porcelain',
),
'Books & Manuscripts' => array(
'All' => 'books-manuscripts',
'Books' => 'books-manuscripts.books',
),
'Carpets, rugs & textiles' => array(
'All' => 'carpets-rugs-textiles',
'European' => 'carpets-rugs-textiles.european',
'Oriental' => 'carpets-rugs-textiles.oriental',
'Rest of the world' => 'carpets-rugs-textiles.rest-of-the-world',
'Scandinavian' => 'carpets-rugs-textiles.scandinavian',
),
'Ceramics & porcelain' => array(
'All' => 'ceramics-porcelain',
'Ceramic ware' => 'ceramics-porcelain.ceramic-ware',
'European' => 'ceramics-porcelain.european',
'Rest of the world' => 'ceramics-porcelain.rest-of-the-world',
'Scandinavian' => 'ceramics-porcelain.scandinavian',
),
'Collectibles' => array(
'All' => 'collectibles',
'Advertising & Retail' => 'collectibles.advertising-retail',
'Memorabilia' => 'collectibles.memorabilia',
'Movies & music' => 'collectibles.movies-music',
'Other' => 'collectibles.other',
'Retro & Popular Culture' => 'collectibles.retro-popular-culture',
'Technica & Nautica' => 'collectibles.technica-nautica',
'Toys' => 'collectibles.toys',
),
'Design' => array(
'All' => 'design',
'Art glass' => 'design.art-glass',
'Furniture' => 'design.furniture',
'Other' => 'design.other',
),
'Folk art' => array(
'All' => 'folk-art',
'All categories' => 'lots',
),
'Furniture' => array(
'All' => 'furniture',
'Armchairs & Sofas' => 'furniture.armchairs-sofas',
'Cabinets & Bureaus' => 'furniture.cabinets-bureaus',
'Chairs' => 'furniture.chairs',
'Garden furniture' => 'furniture.garden-furniture',
'Mirrors' => 'furniture.mirrors',
'Other' => 'furniture.other',
'Shelves & Book cases' => 'furniture.shelves-book-cases',
'Tables' => 'furniture.tables',
),
'Glassware' => array(
'All' => 'glassware',
'Glassware' => 'glassware.glassware',
'Other' => 'glassware.other',
),
'Jewellery' => array(
'All' => 'jewellery',
'Bracelets' => 'jewellery.bracelets',
'Brooches' => 'jewellery.brooches',
'Earrings' => 'jewellery.earrings',
'Necklaces & Pendants' => 'jewellery.necklaces-pendants',
'Other' => 'jewellery.other',
'Rings' => 'jewellery.rings',
),
'Lighting' => array(
'All' => 'lighting',
'Candle sticks & Candelabras' => 'lighting.candle-sticks-candelabras',
'Ceiling lights' => 'lighting.ceiling-lights',
'Chandeliers' => 'lighting.chandeliers',
'Floor lights' => 'lighting.floor-lights',
'Other' => 'lighting.other',
'Table lights' => 'lighting.table-lights',
'Wall lights' => 'lighting.wall-lights',
),
'Militaria' => array(
'All' => 'militaria',
'Honors & Medals' => 'militaria.honors-medals',
'Other militaria' => 'militaria.other-militaria',
'Weaponry' => 'militaria.weaponry',
),
'Miscellaneous' => array(
'All' => 'miscellaneous',
'Brass, Copper & Pewter' => 'miscellaneous.brass-copper-pewter',
'Nickel silver' => 'miscellaneous.nickel-silver',
'Oriental' => 'miscellaneous.oriental',
'Other' => 'miscellaneous.other',
),
'Silver' => array(
'All' => 'silver',
'Candle sticks' => 'silver.candle-sticks',
'Cups & Bowls' => 'silver.cups-bowls',
'Cutlery' => 'silver.cutlery',
'Other' => 'silver.other',
),
'Timepieces' => array(
'All' => 'timepieces',
'Other' => 'timepieces.other',
'Pocket watches' => 'timepieces.pocket-watches',
'Table clocks' => 'timepieces.table-clocks',
'Wrist watches' => 'timepieces.wrist-watches',
),
'Vintage & Fashion' => array(
'All' => 'vintage-fashion',
'Accessories' => 'vintage-fashion.accessories',
'Bags & Trunks' => 'vintage-fashion.bags-trunks',
'Clothes' => 'vintage-fashion.clothes',
),
)
),
'sort_order' => array(
'name' => 'Sort order',
'type' => 'list',
'values' => array(
'Ending soon' => 'ending',
'Most recent' => 'recent',
'Most bids' => 'most',
'Fewest bids' => 'fewest',
'Lowest price' => 'lowest',
'Highest price' => 'highest',
'Lowest estimate' => 'low',
'Highest estimate' => 'high',
'Alphabetical' => 'alphabetical',
),
),
'language' => array(
'name' => 'Language',
'type' => 'list',
'values' => array(
'English' => 'en',
'Swedish' => 'sv',
'Finnish' => 'fi'
),
),
));
const CACHE_TIMEOUT = 3600; // 1 hour
private $title;
public function collectData()
{
$baseUrl = 'https://www.bukowskis.com';
$category = $this->getInput('category');
$language = $this->getInput('language');
$sort_order = $this->getInput('sort_order');
$url = $baseUrl . '/' . $language . '/lots';
if ($category)
$url = $url . '/category/' . $category;
if ($sort_order)
$url = $url . '/sort/' . $sort_order;
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request: ' . $url);
$this->title = htmlspecialchars_decode($html->find('title', 0)->innertext);
foreach ($html->find('div.c-lot-index-lot') as $lot) {
$title = $lot->find('a.c-lot-index-lot__title', 0)->plaintext;
$relative_url = $lot->find('a.c-lot-index-lot__link', 0)->href;
$images = json_decode(
htmlspecialchars_decode(
$lot
->find('img.o-aspect-ratio__image', 0)
->getAttribute('data-thumbnails')
)
);
$this->items[] = array(
'title' => $title,
'uri' => $baseUrl . $relative_url,
'uid' => $lot->getAttribute('data-lot-id'),
'content' => count($images) > 0 ? "<img src='$images[0]'/><br/>$title" : $title,
'enclosures' => array_slice($images, 1),
);
}
}
public function getName()
{
return $this->title ?: parent::getName();
}
}

View File

@@ -17,6 +17,7 @@ class BundesbankBridge extends BridgeAbstract {
self::PARAM_LANG => array( self::PARAM_LANG => array(
'name' => 'Language', 'name' => 'Language',
'type' => 'list', 'type' => 'list',
'required' => true,
'defaultValue' => self::LANG_DE, 'defaultValue' => self::LANG_DE,
'values' => array( 'values' => array(
'English' => self::LANG_EN, 'English' => self::LANG_EN,
@@ -82,4 +83,5 @@ class BundesbankBridge extends BridgeAbstract {
} }
} }
} }

View File

@@ -1,63 +0,0 @@
<?php
class CNETFranceBridge extends FeedExpander
{
const MAINTAINER = 'leomaradan';
const NAME = 'CNET France';
const URI = 'https://www.cnetfrance.fr/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'CNET France RSS with filters';
const PARAMETERS = array(
'filters' => array(
'title' => array(
'name' => 'Exclude by title',
'required' => false,
'title' => 'Title term, separated by semicolon (;)',
'defaultValue' => 'bon plan;bons plans;au meilleur prix;des meilleures offres;Amazon Prime Day;RED by SFR ou B&You'
),
'url' => array(
'name' => 'Exclude by url',
'required' => false,
'title' => 'URL term, separated by semicolon (;)',
'defaultValue' => 'bon-plan;bons-plans'
)
)
);
private $bannedTitle = array();
private $bannedURL = array();
public function collectData()
{
$title = $this->getInput('title');
$url = $this->getInput('url');
if ($title !== null) {
$this->bannedTitle = explode(';', $title);
}
if ($url !== null) {
$this->bannedURL = explode(';', $url);
}
$this->collectExpandableDatas('https://www.cnetfrance.fr/feeds/rss/news/');
}
protected function parseItem($feedItem)
{
$item = parent::parseItem($feedItem);
foreach ($this->bannedTitle as $term) {
if (preg_match('/' . $term . '/mi', $item['title']) === 1) {
return null;
}
}
foreach ($this->bannedURL as $term) {
if (preg_match('/' . $term . '/mi', $item['uri']) === 1) {
return null;
}
}
return $item;
}
}

View File

@@ -1,134 +0,0 @@
<?php
class CachetBridge extends BridgeAbstract {
const NAME = 'Cachet Bridge';
const URI = 'https://cachethq.io/';
const DESCRIPTION = 'Returns status updates from any Cachet installation';
const MAINTAINER = 'klimplant';
const PARAMETERS = array(
array(
'host' => 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 = array();
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 = array();
$categories[] = $incident->human_status;
if ($componentName !== '') {
$categories[] = $componentName;
}
$item = array();
$item['uri'] = $permalink;
$item['title'] = $title;
$item['timestamp'] = $timestamp;
$item['content'] = $content;
$item['uid'] = $uid;
$item['categories'] = $categories;
$this->items[] = $item;
}
}
}

View File

@@ -2,7 +2,7 @@
class CastorusBridge extends BridgeAbstract { class CastorusBridge extends BridgeAbstract {
const MAINTAINER = 'logmanoriginal'; const MAINTAINER = 'logmanoriginal';
const NAME = 'Castorus Bridge'; const NAME = 'Castorus Bridge';
const URI = 'https://www.castorus.com'; const URI = 'http://www.castorus.com';
const CACHE_TIMEOUT = 600; // 10min const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = 'Returns the latest changes'; const DESCRIPTION = 'Returns the latest changes';
@@ -83,7 +83,7 @@ class CastorusBridge extends BridgeAbstract {
if(!$html) if(!$html)
returnServerError('Could not load data from ' . self::URI . '!'); returnServerError('Could not load data from ' . self::URI . '!');
$activities = $html->find('div#activite > li'); $activities = $html->find('div#activite/li');
if(!$activities) if(!$activities)
returnServerError('Failed to find activities!'); returnServerError('Failed to find activities!');

View File

@@ -1,84 +0,0 @@
<?php
class CeskaTelevizeBridge extends BridgeAbstract {
const NAME = 'Česká televize Bridge';
const URI = 'https://www.ceskatelevize.cz';
const CACHE_TIMEOUT = 3600;
const DESCRIPTION = 'Return newest videos';
const MAINTAINER = 'kolarcz';
const PARAMETERS = array(
array(
'url' => array(
'name' => 'url to the show',
'required' => true,
'exampleValue' => 'https://www.ceskatelevize.cz/porady/1097181328-udalosti/dily/'
)
)
);
private function fixChars($text) {
return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
}
private function getUploadTimeFromString($string) {
if (strpos($string, 'dnes') !== false) {
return strtotime('today');
} elseif (strpos($string, 'včera') !== false) {
return strtotime('yesterday');
} elseif (!preg_match('/(\d+).\s(\d+).(\s(\d+))?/', $string, $match)) {
returnServerError('Could not get date from Česká televize string');
}
$date = sprintf('%04d-%02d-%02d', isset($match[3]) ? $match[3] : date('Y'), $match[2], $match[1]);
return strtotime($date);
}
public function collectData() {
$url = $this->getInput('url');
$validUrl = '/^(https:\/\/www\.ceskatelevize\.cz\/porady\/\d+-[a-z0-9-]+\/)(dily\/((nove|vysilani)\/)?)?$/';
if (!preg_match($validUrl, $url, $match)) {
returnServerError('Invalid url');
}
$category = isset($match[4]) ? $match[4] : 'nove';
$fixedUrl = "{$match[1]}dily/{$category}/";
$html = getSimpleHTMLDOM($fixedUrl)
or returnServerError('Could not request Česká televize');
$this->feedUri = $fixedUrl;
$this->feedName = str_replace('Přehled dílů — ', '', $this->fixChars($html->find('title', 0)->plaintext));
if ($category !== 'nove') {
$this->feedName .= " ({$category})";
}
foreach ($html->find('.episodes-broadcast-content a.episode_list_item') as $element) {
$itemTitle = $element->find('.episode_list_item-title', 0);
$itemContent = $element->find('.episode_list_item-desc', 0);
$itemDate = $element->find('.episode_list_item-date', 0);
$itemThumbnail = $element->find('img', 0);
$itemUri = self::URI . $element->getAttribute('href');
$item = array(
'title' => $this->fixChars($itemTitle->plaintext),
'uri' => $itemUri,
'content' => '<img src="https:' . $itemThumbnail->getAttribute('src') . '" /><br />'
. $this->fixChars($itemContent->plaintext),
'timestamp' => $this->getUploadTimeFromString($itemDate->plaintext)
);
$this->items[] = $item;
}
}
public function getURI() {
return isset($this->feedUri) ? $this->feedUri : parent::getURI();
}
public function getName() {
return isset($this->feedName) ? $this->feedName : parent::getName();
}
}

View File

@@ -0,0 +1,28 @@
<?php
class ChristianDailyReporterBridge extends BridgeAbstract {
const MAINTAINER = 'rogerdc';
const NAME = 'Christian Daily Reporter Unofficial RSS';
const URI = 'https://www.christiandailyreporter.com/';
const DESCRIPTION = 'The Unofficial Christian Daily Reporter RSS';
// const CACHE_TIMEOUT = 86400; // 1 day
public function getIcon() {
return self::URI . 'images/cdrfavicon.png';
}
public function collectData() {
$uri = 'https://www.christiandailyreporter.com/';
$html = getSimpleHTMLDOM($uri)
or returnServerError('Could not request Christian Daily Reporter.');
foreach($html->find('div.top p a,div.column p a') as $element) {
$item = array();
// Title
$item['title'] = $element->innertext;
// URL
$item['uri'] = $element->href;
$this->items[] = $item;
}
}
}

View File

@@ -3,7 +3,7 @@ class CollegeDeFranceBridge extends BridgeAbstract {
const MAINTAINER = 'pit-fgfjiudghdf'; const MAINTAINER = 'pit-fgfjiudghdf';
const NAME = 'CollegeDeFrance'; const NAME = 'CollegeDeFrance';
const URI = 'https://www.college-de-france.fr/'; const URI = 'http://www.college-de-france.fr/';
const CACHE_TIMEOUT = 10800; // 3h const CACHE_TIMEOUT = 10800; // 3h
const DESCRIPTION = 'Returns the latest audio and video from CollegeDeFrance'; const DESCRIPTION = 'Returns the latest audio and video from CollegeDeFrance';

View File

@@ -1,22 +0,0 @@
<?php
class ComboiosDePortugalBridge extends BridgeAbstract {
const NAME = 'CP | Avisos';
const BASE_URI = 'https://www.cp.pt';
const URI = self::BASE_URI . '/passageiros/pt';
const DESCRIPTION = 'Comboios de Portugal | Avisos';
const MAINTAINER = 'somini';
public function collectData() {
$html = getSimpleHTMLDOM($this->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;
}
}
}

View File

@@ -1,65 +0,0 @@
<?php
class ComicsKingdomBridge extends BridgeAbstract {
const MAINTAINER = 'stjohnjohnson';
const NAME = 'Comics Kingdom Unofficial RSS';
const URI = 'https://www.comicskingdom.com/';
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'Comics Kingdom Unofficial RSS';
const PARAMETERS = array( array(
'comicname' => array(
'name' => 'comicname',
'type' => 'text',
'required' => true
)
));
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI(), array(), array(), true, false)
or returnServerError('Could not request Comics Kingdom: ' . $this->getURI());
// Get author from first page
$author = $html->find('div.author p', 0)->plaintext
or returnServerError('Comics Kingdom comic does not exist: ' . $this->getURI());;
// Get current date/link
$link = $html->find('meta[property=og:url]', 0)->content;
for($i = 0; $i < 5; $i++) {
$item = array();
$page = getSimpleHTMLDOM($link)
or returnServerError('Could not request Comics Kingdom: ' . $link);
$imagelink = $page->find('meta[property=og:image]', 0)->content;
$prevSlug = $page->find('slider-arrow[:is-left-arrow=true]', 0);
$link = $this->getURI() . '/' . $prevSlug->getAttribute('date-slug');
$date = explode('/', $link);
$item['id'] = $imagelink;
$item['uri'] = $link;
$item['author'] = $author;
$item['title'] = 'Comics Kingdom ' . $this->getInput('comicname');
$item['timestamp'] = DateTime::createFromFormat('Y-m-d', $date[count($date) - 1])->getTimestamp();
$item['content'] = '<img src="' . $imagelink . '" />';
$this->items[] = $item;
}
}
public function getURI(){
if(!is_null($this->getInput('comicname'))) {
return self::URI . urlencode($this->getInput('comicname'));
}
return parent::getURI();
}
public function getName(){
if(!is_null($this->getInput('comicname'))) {
return $this->getInput('comicname') . ' - Comics Kingdom';
}
return parent::getName();
}
}

View File

@@ -3,7 +3,7 @@ class CommonDreamsBridge extends FeedExpander {
const MAINTAINER = 'nyutag'; const MAINTAINER = 'nyutag';
const NAME = 'CommonDreams Bridge'; const NAME = 'CommonDreams Bridge';
const URI = 'https://www.commondreams.org/'; const URI = 'http://www.commondreams.org/';
const DESCRIPTION = 'Returns the newest articles.'; const DESCRIPTION = 'Returns the newest articles.';
public function collectData(){ public function collectData(){

View File

@@ -10,20 +10,21 @@ class ContainerLinuxReleasesBridge extends BridgeAbstract {
const BETA = 'beta'; const BETA = 'beta';
const ALPHA = 'alpha'; const ALPHA = 'alpha';
const PARAMETERS = array( const PARAMETERS = [
array( [
'channel' => array( 'channel' => [
'name' => 'Release Channel', 'name' => 'Release Channel',
'type' => 'list', 'type' => 'list',
'required' => true,
'defaultValue' => self::STABLE, 'defaultValue' => self::STABLE,
'values' => array( 'values' => [
'Stable' => self::STABLE, 'Stable' => self::STABLE,
'Beta' => self::BETA, 'Beta' => self::BETA,
'Alpha' => self::ALPHA, 'Alpha' => self::ALPHA,
), ],
) ]
) ]
); ];
private function getReleaseFeed($jsonUrl) { private function getReleaseFeed($jsonUrl) {
$json = getContents($jsonUrl) $json = getContents($jsonUrl)
@@ -39,7 +40,7 @@ class ContainerLinuxReleasesBridge extends BridgeAbstract {
$data = $this->getReleaseFeed($this->getJsonUri()); $data = $this->getReleaseFeed($this->getJsonUri());
foreach ($data as $releaseVersion => $release) { foreach ($data as $releaseVersion => $release) {
$item = array(); $item = [];
$item['uri'] = "https://coreos.com/releases/#$releaseVersion"; $item['uri'] = "https://coreos.com/releases/#$releaseVersion";
$item['title'] = $releaseVersion; $item['title'] = $releaseVersion;

View File

@@ -3,7 +3,7 @@ class CourrierInternationalBridge extends BridgeAbstract {
const MAINTAINER = 'teromene'; const MAINTAINER = 'teromene';
const NAME = 'Courrier International Bridge'; const NAME = 'Courrier International Bridge';
const URI = 'https://www.courrierinternational.com/'; const URI = 'http://CourrierInternational.com/';
const CACHE_TIMEOUT = 300; // 5 min const CACHE_TIMEOUT = 300; // 5 min
const DESCRIPTION = 'Courrier International bridge'; const DESCRIPTION = 'Courrier International bridge';

View File

@@ -1,227 +0,0 @@
<?php
class CrewbayBridge extends BridgeAbstract {
const MAINTAINER = 'couraudt';
const NAME = 'Crewbay Bridge';
const URI = 'https://www.crewbay.com';
const DESCRIPTION = 'Returns the newest sailing offers.';
const PARAMETERS = array(
array(
'keyword' => array(
'name' => 'Filter by keyword',
'title' => 'Enter the keyword to filter here'
),
'type' => array(
'name' => 'Type of search',
'title' => 'Choose between finding a boat or a crew',
'type' => 'list',
'values' => array(
'Find a boat' => 'boats',
'Find a crew' => 'crew'
)
),
'status' => array(
'name' => 'Status on the boat',
'title' => 'Choose between recreational or professional classified ads',
'type' => 'list',
'values' => array(
'Recreational' => 'recreational',
'Professional' => 'professional'
)
),
'recreational_position' => array(
'name' => 'Recreational position wanted',
'title' => 'Filter by recreational position you wanted aboard',
'required' => false,
'type' => 'list',
'values' => array(
'' => '',
'Amateur Crew' => 'Amateur Crew',
'Friendship' => 'Friendship',
'Competent Crew' => 'Competent Crew',
'Racing' => 'Racing',
'Voluntary work' => 'Voluntary work',
'Mile building' => 'Mile building'
)
),
'professional_position' => array(
'name' => 'Professional position wanted',
'title' => 'Filter by professional position you wanted aboard',
'required' => false,
'type' => 'list',
'values' => array(
'' => '',
'1st Engineer' => '1st Engineer',
'1st Mate' => '1st Mate',
'Beautician' => 'Beautician',
'Bosun' => 'Bosun',
'Captain' => 'Captain',
'Chef' => 'Chef',
'Steward(ess)' => 'Steward(ess)',
'Deckhand' => 'Deckhand',
'Delivery Crew' => 'Delivery Crew',
'Dive Instructor' => 'Dive Instructor',
'Masseur' => 'Masseur',
'Medical Staff' => 'Medical Staff',
'Nanny' => 'Nanny',
'Navigator' => 'Navigator',
'Racing Crew' => 'Racing Crew',
'Teacher' => 'Teacher',
'Electrical Engineer' => 'Electrical Engineer',
'Fitter' => 'Fitter',
'2nd Engineer' => '2nd Engineer',
'3rd Engineer' => '3rd Engineer',
'Lead Deckhand' => 'Lead Deckhand',
'Security Officer' => 'Security Officer',
'O.O.W' => 'O.O.W',
'1st Officer' => '1st Officer',
'2nd Officer' => '2nd Officer',
'3rd Officer' => '3rd Officer',
'Captain/Engineer' => 'Captain/Engineer',
'Hairdresser' => 'Hairdresser',
'Fitness Trainer' => 'Fitness Trainer',
'Laundry' => 'Laundry',
'Solo Steward/ess' => 'Solo Steward/ess',
'Stew/Deck' => 'Stew/Deck',
'2nd Steward/ess' => '2nd Steward/ess',
'3rd Steward/ess' => '3rd Steward/ess',
'Chief Steward/ess' => 'Chief Steward/ess',
'Head Housekeeper' => 'Head Housekeeper',
'Purser' => 'Purser',
'Cook' => 'Cook',
'Cook/Stew' => 'Cook/Stew',
'2nd Chef' => '2nd Chef',
'Head Chef' => 'Head Chef',
'Administrator' => 'Administrator',
'P.A' => 'P.A',
'Villa staff' => 'Villa staff',
'Housekeeping/Stew' => 'Housekeeping/Stew',
'Stew/Beautician' => 'Stew/Beautician',
'Stew/Masseuse' => 'Stew/Masseuse',
'Manager' => 'Manager',
'Sailing instructor' => 'Sailing instructor'
)
)
)
);
public function collectData() {
$url = $this->getURI();
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
$annonces = $html->find('#SearchResults div.result');
$limit = 0;
foreach ($annonces as $annonce) {
$detail = $annonce->find('.btn--profile', 0);
$htmlDetail = getSimpleHTMLDOMCached($detail->href);
if (!empty($this->getInput('recreational_position')) || !empty($this->getInput('professional_position'))) {
if ($this->getInput('type') == 'boats') {
if ($this->getInput('status') == 'professional') {
$positions = array($annonce->find('.title .position', 0)->plaintext);
} else {
$positions = array(str_replace('Wanted:', '', $annonce->find('.content li', 0)->plaintext));
}
} else {
$list = $htmlDetail->find('.viewer-details .viewer-list');
$positions = explode("\r\n", end($list)->find('span.value', 0)->plaintext);
}
$found = false;
$keyword = $this->getInput('status') == 'professional' ? 'professional_position' : 'recreational_position';
foreach ($positions as $position) {
if (strpos(trim($position), $this->getInput($keyword)) !== false) {
$found = true;
break;
}
}
if (!$found) {
continue;
}
}
$item = array();
if ($this->getInput('type') == 'boats') {
$titleSelector = '.title h2';
} else {
$titleSelector = '.layout__item h2';
}
$userName = $annonce->find('.result--description a', 0)->plaintext;
$annonceTitle = trim($annonce->find($titleSelector, 0)->plaintext);
if (empty($annonceTitle)) {
$item['title'] = $userName;
} else {
$item['title'] = $userName . ' - ' . $annonceTitle;
}
$item['uri'] = $detail->href;
$images = $annonce->find('.avatar img');
$item['enclosures'] = array(end($images)->getAttribute('src'));
$content = $htmlDetail->find('.viewer-intro--info', 0)->innertext;
$sections = $htmlDetail->find('.viewer-container .viewer-section');
foreach ($sections as $section) {
if ($section->find('.viewer-section-title', 0)) {
$class = str_replace('viewer-', '', explode(' ', $section->getAttribute('class'))[0]);
if (!in_array($class, array('apply', 'photos', 'reviews', 'contact', 'experience', 'qa'))) {
// Basic sections
$content .= $section->find('.viewer-section-title h3', 0)->outertext;
$content .= $section->find('.viewer-section-content', 0)->innertext;
}
} else {
// Info section
$content .= $section->find('.viewer-section-content h3', 0)->outertext;
$content .= $section->find('.viewer-section-content p', 0)->outertext;
}
}
if (!empty($this->getInput('keyword'))) {
$keyword = strtolower($this->getInput('keyword'));
if (strpos(strtolower($item['title']), $keyword) === false) {
if (strpos(strtolower($content), $keyword) === false) {
continue;
}
}
}
$item['content'] = $content;
$tags = $htmlDetail->find('li.viewer-tags--tag');
foreach ($tags as $tag) {
if (!isset($item['categories'])) {
$item['categories'] = array();
}
$text = trim($tag->plaintext);
if (!in_array($text, $item['categories'])) {
$item['categories'][] = $text;
}
}
$this->items[] = $item;
$limit += 1;
if ($limit == 10) break;
}
}
public function getURI() {
$uri = parent::getURI();
if ($this->getInput('type') == 'boats') {
$uri .= '/boats';
} else {
$uri .= '/crew';
}
if ($this->getInput('status') == 'professional') {
$uri .= '/professional';
} else {
$uri .= '/recreational';
}
return $uri;
}
}

View File

@@ -1,109 +0,0 @@
<?php
class CuriousCatBridge extends BridgeAbstract {
const NAME = 'Curious Cat Bridge';
const URI = 'https://curiouscat.me';
const DESCRIPTION = 'Returns list of newest questions and answers for a user profile';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(array(
'username' => array(
'name' => 'Username',
'type' => 'text',
'required' => true,
'exampleValue' => 'koethekoethe',
)
));
const CACHE_TIMEOUT = 3600;
public function collectData() {
$url = self::URI . '/api/v2/profile?username=' . urlencode($this->getInput('username'));
$apiJson = getContents($url)
or returnServerError('Could not request: ' . $url);
$apiData = json_decode($apiJson, true);
foreach($apiData['posts'] as $post) {
$item = array();
$item['author'] = 'Anonymous';
if ($post['senderData']['id'] !== false) {
$item['author'] = $post['senderData']['username'];
}
$item['uri'] = $this->getURI() . '/post/' . $post['id'];
$item['title'] = $this->ellipsisTitle($post['comment']);
$item['content'] = $this->processContent($post);
$item['timestamp'] = $post['timestamp'];
$this->items[] = $item;
}
}
public function getURI() {
if (!is_null($this->getInput('username'))) {
return self::URI . '/' . $this->getInput('username');
}
return parent::getURI();
}
public function getName() {
if (!is_null($this->getInput('username'))) {
return $this->getInput('username') . ' - Curious Cat';
}
return parent::getName();
}
private function processContent($post) {
$author = 'Anonymous';
if ($post['senderData']['id'] !== false) {
$authorUrl = self::URI . '/' . $post['senderData']['username'];
$author = <<<EOD
<a href="{$authorUrl}">{$post['senderData']['username']}</a>
EOD;
}
$question = $this->formatUrls($post['comment']);
$answer = $this->formatUrls($post['reply']);
$content = <<<EOD
<p>{$author} asked:</p>
<blockquote>{$question}</blockquote><br/>
<p>{$post['addresseeData']['username']} answered:</p>
<blockquote>{$answer}</blockquote>
EOD;
return $content;
}
private function ellipsisTitle($text) {
$length = 150;
if (strlen($text) > $length) {
$text = explode('<br>', wordwrap($text, $length, '<br>'));
return $text[0] . '...';
}
return $text;
}
private function formatUrls($content) {
return preg_replace(
'/(http[s]{0,1}\:\/\/[a-zA-Z0-9.\/\?\&=\-_]{4,})/ims',
'<a target="_blank" href="$1" target="_blank">$1</a> ',
$content
);
}
}

View File

@@ -4,7 +4,7 @@ class DailymotionBridge extends BridgeAbstract {
const MAINTAINER = 'mitsukarenai'; const MAINTAINER = 'mitsukarenai';
const NAME = 'Dailymotion Bridge'; const NAME = 'Dailymotion Bridge';
const URI = 'https://www.dailymotion.com/'; const URI = 'https://www.dailymotion.com/';
const CACHE_TIMEOUT = 3600; // 1h const CACHE_TIMEOUT = 10800; // 3h
const DESCRIPTION = 'Returns the 5 newest videos by username/playlist or search'; const DESCRIPTION = 'Returns the 5 newest videos by username/playlist or search';
const PARAMETERS = array ( const PARAMETERS = array (
@@ -27,64 +27,47 @@ class DailymotionBridge extends BridgeAbstract {
), ),
'pa' => array( 'pa' => array(
'name' => 'Page', 'name' => 'Page',
'type' => 'number', 'type' => 'number'
'defaultValue' => 1,
) )
) )
); );
private $feedName = ''; protected function getMetadata($id){
$metadata = array();
$html2 = getSimpleHTMLDOM(self::URI . 'video/' . $id);
if(!$html2) {
return $metadata;
}
private $apiUrl = 'https://api.dailymotion.com'; $metadata['title'] = $html2->find('meta[property=og:title]', 0)->getAttribute('content');
private $apiFields = 'created_time,description,id,owner.screenname,tags,thumbnail_url,title,url'; $metadata['timestamp'] = strtotime(
$html2->find('meta[property=video:release_date]', 0)->getAttribute('content')
);
$metadata['thumbnailUri'] = $html2->find('meta[property=og:image]', 0)->getAttribute('content');
$metadata['uri'] = $html2->find('meta[property=og:url]', 0)->getAttribute('content');
return $metadata;
}
public function getIcon() { public function getIcon() {
return 'https://static1-ssl.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vf806ca4ed0deed812'; return 'https://static1-ssl.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vf806ca4ed0deed812';
} }
public function collectData() { public function collectData(){
$html = '';
if ($this->queriedContext === 'By username' || $this->queriedContext === 'By playlist id') { $limit = 5;
$count = 0;
$apiJson = getContents($this->getApiUrl())
or returnServerError('Could not request: ' . $this->getApiUrl());
$apiData = json_decode($apiJson, true);
$this->feedName = $this->getPlaylistTitle($this->getInput('p'));
foreach ($apiData['list'] as $apiItem) {
$item = array();
$item['uri'] = $apiItem['url'];
$item['uid'] = $apiItem['id'];
$item['title'] = $apiItem['title'];
$item['timestamp'] = $apiItem['created_time'];
$item['author'] = $apiItem['owner.screenname'];
$item['content'] = '<p><a href="' . $apiItem['url'] . '">
<img src="' . $apiItem['thumbnail_url'] . '"></a></p><p>' . $apiItem['description'] . '</p>';
$item['categories'] = $apiItem['tags'];
$item['enclosures'][] = $apiItem['thumbnail_url'];
$this->items[] = $item;
}
}
if ($this->queriedContext === 'From search results') {
$html = getSimpleHTMLDOM($this->getURI()) $html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request Dailymotion.'); or returnServerError('Could not request Dailymotion.');
foreach($html->find('div.media a.preview_link') as $element) { foreach($html->find('div.media a.preview_link') as $element) {
if($count < $limit) {
$item = array(); $item = array();
$item['id'] = str_replace('/video/', '', strtok($element->href, '_')); $item['id'] = str_replace('/video/', '', strtok($element->href, '_'));
$metadata = $this->getMetadata($item['id']); $metadata = $this->getMetadata($item['id']);
if(empty($metadata)) { if(empty($metadata)) {
continue; continue;
} }
$item['uri'] = $metadata['uri']; $item['uri'] = $metadata['uri'];
$item['title'] = $metadata['title']; $item['title'] = $metadata['title'];
$item['timestamp'] = $metadata['timestamp']; $item['timestamp'] = $metadata['timestamp'];
@@ -100,26 +83,18 @@ class DailymotionBridge extends BridgeAbstract {
. '</a>'; . '</a>';
$this->items[] = $item; $this->items[] = $item;
$count++;
if (count($this->items) >= 5) {
break;
}
} }
} }
} }
public function getName() { public function getName(){
switch($this->queriedContext) { switch($this->queriedContext) {
case 'By username': case 'By username':
$specific = $this->getInput('u'); $specific = $this->getInput('u');
break; break;
case 'By playlist id': case 'By playlist id':
$specific = strtok($this->getInput('p'), '_'); $specific = strtok($this->getInput('p'), '_');
if ($this->feedName) {
$specific = $this->feedName;
}
break; break;
case 'From search results': case 'From search results':
$specific = $this->getInput('s'); $specific = $this->getInput('s');
@@ -127,77 +102,26 @@ class DailymotionBridge extends BridgeAbstract {
default: return parent::getName(); default: return parent::getName();
} }
return $specific . ' : Dailymotion'; return $specific . ' : Dailymotion Bridge';
} }
public function getURI(){ public function getURI(){
$uri = self::URI; $uri = self::URI;
switch($this->queriedContext) { switch($this->queriedContext) {
case 'By username': case 'By username':
$uri .= 'user/' . urlencode($this->getInput('u')); $uri .= 'user/' . urlencode($this->getInput('u')) . '/1';
break; break;
case 'By playlist id': case 'By playlist id':
$uri .= 'playlist/' . urlencode(strtok($this->getInput('p'), '_')); $uri .= 'playlist/' . urlencode(strtok($this->getInput('p'), '_'));
break; break;
case 'From search results': case 'From search results':
$uri .= 'search/' . urlencode($this->getInput('s')); $uri .= 'search/' . urlencode($this->getInput('s'));
if($this->getInput('pa')) {
if(!is_null($this->getInput('pa'))) { $uri .= '/' . $this->getInput('pa');
$pa = $this->getInput('pa');
if ($this->getInput('pa') < 1) {
$pa = 1;
}
$uri .= '/' . $pa;
} }
break; break;
default: return parent::getURI(); default: return parent::getURI();
} }
return $uri; return $uri;
} }
private function getMetadata($id) {
$metadata = array();
$html = getSimpleHTMLDOM(self::URI . 'video/' . $id);
if(!$html) {
return $metadata;
}
$metadata['title'] = $html->find('meta[property=og:title]', 0)->getAttribute('content');
$metadata['timestamp'] = strtotime(
$html->find('meta[property=video:release_date]', 0)->getAttribute('content')
);
$metadata['thumbnailUri'] = $html->find('meta[property=og:image]', 0)->getAttribute('content');
$metadata['uri'] = $html->find('meta[property=og:url]', 0)->getAttribute('content');
return $metadata;
}
private function getPlaylistTitle($id) {
$title = '';
$url = self::URI . 'playlist/' . $id;
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request: ' . $url);
$title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
return $title;
}
private function getApiUrl() {
switch($this->queriedContext) {
case 'By username':
return $this->apiUrl . '/user/' . $this->getInput('u')
. '/videos?fields=' . urlencode($this->apiFields) . '&availability=1&sort=recent&limit=5';
break;
case 'By playlist id':
return $this->apiUrl . '/playlist/' . $this->getInput('p')
. '/videos?fields=' . urlencode($this->apiFields) . '&limit=5';
break;
}
}
} }

View File

@@ -40,7 +40,7 @@ class DanbooruBridge extends BridgeAbstract {
defaultLinkTo($element, $this->getURI()); defaultLinkTo($element, $this->getURI());
$item = array(); $item = array();
$item['uri'] = html_entity_decode($element->find('a', 0)->href); $item['uri'] = $element->find('a', 0)->href;
$item['postid'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE)); $item['postid'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE));
$item['timestamp'] = time(); $item['timestamp'] = time();
$thumbnailUri = $element->find('img', 0)->src; $thumbnailUri = $element->find('img', 0)->src;

View File

@@ -1,81 +0,0 @@
<?php
class DarkReadingBridge extends FeedExpander {
const MAINTAINER = 'ORelio';
const NAME = 'Dark Reading Bridge';
const URI = 'https://www.darkreading.com/';
const DESCRIPTION = 'Returns the newest articles from Dark Reading';
const PARAMETERS = array( array(
'feed' => array(
'name' => 'Feed',
'type' => 'list',
'values' => array(
'All Dark Reading Stories' => '000_AllArticles',
'Attacks/Breaches' => '644_Attacks/Breaches',
'Application Security' => '645_Application%20Security',
'Database Security' => '646_Database%20Security',
'Cloud' => '647_Cloud',
'Endpoint' => '648_Endpoint',
'Authentication' => '649_Authentication',
'Privacy' => '650_Privacy',
'Mobile' => '651_Mobile',
'Perimeter' => '652_Perimeter',
'Risk' => '653_Risk',
'Compliance' => '654_Compliance',
'Operations' => '655_Operations',
'Careers and People' => '656_Careers%20and%20People',
'Identity and Access Management' => '657_Identity%20and%20Access%20Management',
'Analytics' => '658_Analytics',
'Threat Intelligence' => '659_Threat%20Intelligence',
'Security Monitoring' => '660_Security%20Monitoring',
'Vulnerabilities / Threats' => '661_Vulnerabilities%20/%20Threats',
'Advanced Threats' => '662_Advanced%20Threats',
'Insider Threats' => '663_Insider%20Threats',
'Vulnerability Management' => '664_Vulnerability%20Management',
)
)
));
public function collectData(){
$feed = $this->getInput('feed');
$feed_splitted = explode('_', $feed);
$feed_id = $feed_splitted[0];
$feed_name = $feed_splitted[1];
if(empty($feed) || !ctype_digit($feed_id) || !preg_match('/[A-Za-z%20\/]/', $feed_name)) {
returnClientError('Invalid feed, please check the "feed" parameter.');
}
$feed_url = $this->getURI() . 'rss_simple.asp';
if ($feed_id != '000') {
$feed_url .= '?f_n=' . $feed_id . '&f_ln=' . $feed_name;
}
$this->collectExpandableDatas($feed_url);
}
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
if (empty($item['content']))
return null; //ignore dummy articles
$article = getSimpleHTMLDOMCached($item['uri'])
or returnServerError('Could not request Dark Reading: ' . $item['uri']);
$item['content'] = $this->extractArticleContent($article);
$item['enclosures'] = array(); //remove author profile picture
return $item;
}
private function extractArticleContent($article){
$content = $article->find('div#article-main', 0)->innertext;
foreach (array(
'<div class="divsplitter',
'<div style="float: left; margin-right: 2px;',
'<div class="more-insights',
'<div id="more-insights',
) as $div_start) {
$content = stripRecursiveHTMLSection($content, 'div', $div_start);
}
$content = stripWithDelimiters($content, '<h1 ', '</h1>');
return $content;
}
}

View File

@@ -1,24 +0,0 @@
<?php
class DaveRamseyBlogBridge extends BridgeAbstract {
const MAINTAINER = 'johnpc';
const NAME = 'Dave Ramsey Blog';
const URI = 'https://www.daveramsey.com/blog';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Returns blog posts from daveramsey.com';
public function collectData()
{
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request daveramsey.com.');
foreach ($html->find('.Post') as $element) {
$this->items[] = array(
'uri' => 'https://www.daveramsey.com' . $element->find('header > a', 0)->href,
'title' => $element->find('header > h2 > a', 0)->plaintext,
'tags' => $element->find('.Post-topic', 0)->plaintext,
'content' => $element->find('.Post-body', 0)->plaintext,
);
}
}
}

View File

@@ -1,27 +0,0 @@
<?php
class DavesTrailerPageBridge extends BridgeAbstract {
const MAINTAINER = 'johnnygroovy';
const NAME = 'Daves Trailer Page Bridge';
const URI = 'https://www.davestrailerpage.co.uk/';
const DESCRIPTION = 'Last trailers in HD thanks to Dave.';
public function collectData(){
$html = getSimpleHTMLDOM(static::URI)
or returnClientError('No results for this query.');
foreach ($html->find('tr[!align]') as $tr) {
$item = array();
// title
$item['title'] = $tr->find('td', 0)->find('b', 0)->plaintext;
// content
$item['content'] = $tr->find('ul', 1);
// uri
$item['uri'] = $tr->find('a', 3)->getAttribute('href');
$this->items[] = $item;
}
}
}

View File

@@ -15,23 +15,27 @@ class DealabsBridge extends PepperBridgeAbstract {
'hide_expired' => array( 'hide_expired' => array(
'name' => 'Masquer les éléments expirés', 'name' => 'Masquer les éléments expirés',
'type' => 'checkbox', 'type' => 'checkbox',
'required' => 'true'
), ),
'hide_local' => array( 'hide_local' => array(
'name' => 'Masquer les deals locaux', 'name' => 'Masquer les deals locaux',
'type' => 'checkbox', 'type' => 'checkbox',
'title' => 'Masquer les deals en magasins physiques', 'title' => 'Masquer les deals en magasins physiques',
'required' => 'true'
), ),
'priceFrom' => array( 'priceFrom' => array(
'name' => 'Prix minimum', 'name' => 'Prix minimum',
'type' => 'text', 'type' => 'text',
'title' => 'Prix mnimum en euros', 'title' => 'Prix mnimum en euros',
'required' => false 'required' => 'false',
'defaultValue' => ''
), ),
'priceTo' => array( 'priceTo' => array(
'name' => 'Prix maximum', 'name' => 'Prix maximum',
'type' => 'text', 'type' => 'text',
'title' => 'Prix maximum en euros', 'title' => 'Prix maximum en euros',
'required' => false 'required' => 'false',
'defaultValue' => ''
), ),
), ),
@@ -39,6 +43,7 @@ class DealabsBridge extends PepperBridgeAbstract {
'group' => array( 'group' => array(
'name' => 'Groupe', 'name' => 'Groupe',
'type' => 'list', 'type' => 'list',
'required' => 'true',
'title' => 'Groupe dont il faut afficher les deals', 'title' => 'Groupe dont il faut afficher les deals',
'values' => array( 'values' => array(
'Abonnements internet' => 'abonnements-internet', 'Abonnements internet' => 'abonnements-internet',
@@ -954,6 +959,7 @@ class DealabsBridge extends PepperBridgeAbstract {
'order' => array( 'order' => array(
'name' => 'Trier par', 'name' => 'Trier par',
'type' => 'list', 'type' => 'list',
'required' => 'true',
'title' => 'Ordre de tri des deals', 'title' => 'Ordre de tri des deals',
'values' => array( 'values' => array(
'Du deal le plus Hot au moins Hot' => '', 'Du deal le plus Hot au moins Hot' => '',
@@ -1145,7 +1151,7 @@ class PepperBridgeAbstract extends BridgeAbstract {
} else { } else {
foreach ($list as $deal) { foreach ($list as $deal) {
$item = array(); $item = array();
$item['uri'] = $deal->find('div[class*=threadGrid-title]', 0)->find('a', 0)->href; $item['uri'] = $deal->find('div[class=threadGrid-title]', 0)->find('a', 0)->href;
$item['title'] = $deal->find('a[class*=' . $selectorLink . ']', 0 $item['title'] = $deal->find('a[class*=' . $selectorLink . ']', 0
)->plaintext; )->plaintext;
$item['author'] = $deal->find('span.thread-username', 0)->plaintext; $item['author'] = $deal->find('span.thread-username', 0)->plaintext;
@@ -1218,6 +1224,7 @@ class PepperBridgeAbstract extends BridgeAbstract {
} }
} }
/** /**
* Get the Shipping costs from a Deal if it exists * Get the Shipping costs from a Deal if it exists
* @return string String of the deal shipping Cost * @return string String of the deal shipping Cost
@@ -1376,11 +1383,8 @@ class PepperBridgeAbstract extends BridgeAbstract {
// Add the Hour and minutes // Add the Hour and minutes
$date_str .= ' 00:00'; $date_str .= ' 00:00';
$date = DateTime::createFromFormat('j F Y H:i', $date_str); $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(); return $date->getTimestamp();
} }
@@ -1453,6 +1457,8 @@ class PepperBridgeAbstract extends BridgeAbstract {
} }
} }
/** /**
* This is some "localisation" function that returns the needed content using * This is some "localisation" function that returns the needed content using
* the "$lang" class variable in the local class * the "$lang" class variable in the local class
@@ -1466,4 +1472,5 @@ class PepperBridgeAbstract extends BridgeAbstract {
return null; return null;
} }
} }
} }

166
bridges/DemonoidBridge.php Normal file
View File

@@ -0,0 +1,166 @@
<?php
class DemonoidBridge extends BridgeAbstract {
const MAINTAINER = 'metaMMA';
const NAME = 'Demonoid';
const URI = 'https://www.demonoid.pw/';
const DESCRIPTION = 'Returns results from search';
const PARAMETERS = array(array(
'q' => array(
'name' => 'keywords',
'exampleValue' => 'keyword1 keyword2…',
'required' => true,
),
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 0,
'Movies' => 1,
'Music' => 2,
'TV' => 3,
'Games' => 4,
'Applications' => 5,
'Pictures' => 8,
'Anime' => 9,
'Comics' => 10,
'Books' => 11,
'Audiobooks' => 17
)
)
), array(
'catOnly' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 0,
'Movies' => 1,
'Music' => 2,
'TV' => 3,
'Games' => 4,
'Applications' => 5,
'Pictures' => 8,
'Anime' => 9,
'Comics' => 10,
'Books' => 11,
'Audiobooks' => 17
)
)
), array(
'userid' => array(
'name' => 'user id',
'exampleValue' => '00000',
'required' => true,
'type' => 'number'
),
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 0,
'Movies' => 1,
'Music' => 2,
'TV' => 3,
'Games' => 4,
'Applications' => 5,
'Pictures' => 8,
'Anime' => 9,
'Comics' => 10,
'Books' => 11,
'Audiobooks' => 17
)
)
)
);
public function collectData() {
if(!empty($this->getInput('q'))) {
$html = getSimpleHTMLDOM(
self::URI .
'files/?category=' .
rawurlencode($this->getInput('category')) .
'&subcategory=All&quality=All&seeded=2&external=2&query=' .
urlencode($this->getInput('q')) .
'&uid=0&sort='
) or returnServerError('Could not request Demonoid.');
} elseif(!empty($this->getInput('catOnly'))) {
$html = getSimpleHTMLDOM(
self::URI .
'files/?uid=0&category=' .
rawurlencode($this->getInput('catOnly')) .
'&subcategory=0&language=0&seeded=2&quality=0&query=&sort='
) or returnServerError('Could not request Demonoid.');
} elseif(!empty($this->getInput('userid'))) {
$html = getSimpleHTMLDOM(
self::URI .
'files/?uid=' .
rawurlencode($this->getInput('userid')) .
'&seeded=2'
) or returnServerError('Could not request Demonoid.');
} else {
returnServerError('Invalid parameters !');
}
if(preg_match('~No torrents found~', $html)) {
return;
}
$table = $html->find('td[class=ctable_content_no_pad]', 0);
$cursorCount = 4;
$elementCount = 0;
while($elementCount != 40) {
$elementCount++;
$currentElement = $table->find('tr', $cursorCount);
if(preg_match('~items total~', $currentElement)) {
break;
}
$item = array();
//Do we have a date ?
if(preg_match('~Added.*?(.*)~', $currentElement->plaintext, $dateStr)) {
if(preg_match('~today~', $dateStr[0])) {
date_default_timezone_set('UTC');
$timestamp = mktime(0, 0, 0, gmdate('n'), gmdate('j'), gmdate('Y'));
} else {
preg_match('~(?<=ed on ).*\d+~', $currentElement->plaintext, $fullDateStr);
date_default_timezone_set('UTC');
$dateObj = strptime($fullDateStr[0], '%A, %b %d, %Y');
$timestamp = mktime(0, 0, 0, $dateObj['tm_mon'] + 1, $dateObj['tm_mday'], 1900 + $dateObj['tm_year']);
}
$cursorCount++;
}
$content = $table->find('tr', $cursorCount)->find('a', 1);
$cursorCount++;
$torrentInfo = $table->find('tr', $cursorCount);
$item['timestamp'] = $timestamp;
$item['title'] = $content->plaintext;
$item['id'] = self::URI . $content->href;
$item['uri'] = self::URI . $content->href;
$item['author'] = $torrentInfo->find('a[class=user]', 0)->plaintext;
$item['seeders'] = $torrentInfo->find('font[class=green]', 0)->plaintext;
$item['leechers'] = $torrentInfo->find('font[class=red]', 0)->plaintext;
$item['size'] = $torrentInfo->find('td', 3)->plaintext;
$item['content'] = 'Uploaded by ' . $item['author']
. ' , Size ' . $item['size']
. '<br>seeders: '
. $item['seeders']
. ' | leechers: '
. $item['leechers']
. '<br><a href="'
. $item['id']
. '">info page</a>';
$this->items[] = $item;
$cursorCount++;
}
}
}

View File

@@ -1,113 +0,0 @@
<?php
class DerpibooruBridge extends BridgeAbstract {
const NAME = 'Derpibooru Bridge';
const URI = 'https://derpibooru.org/';
const DESCRIPTION = 'Returns newest posts from a Derpibooru search';
const CACHE_TIMEOUT = 300; // 5min
const MAINTAINER = 'Roliga';
const PARAMETERS = array(
array(
'f' => array(
'name' => 'Filter',
'type' => 'list',
'values' => array(
'Everything' => 56027,
'18+ R34' => 37432,
'Legacy Default' => 37431,
'18+ Dark' => 37429,
'Maximum Spoilers' => 37430,
'Default' => 100073
),
'defaultValue' => 56027
),
'q' => array(
'name' => 'Query',
'required' => true
)
)
);
public function detectParameters($url){
$params = array();
// Search page e.g. https://derpibooru.org/search?q=cute
$regex = '/^(https?:\/\/)?(www\.)?derpibooru.org\/search.+q=([^\/&?\n]+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['q'] = urldecode($matches[3]);
return $params;
}
// Tag page, e.g. https://derpibooru.org/tags/artist-colon-devinian
$regex = '/^(https?:\/\/)?(www\.)?derpibooru.org\/tags\/([^\/&?\n]+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['q'] = str_replace('-colon-', ':', urldecode($matches[3]));
return $params;
}
return null;
}
public function getName(){
if(!is_null($this->getInput('q'))) {
return 'Derpibooru search for: '
. $this->getInput('q');
} else {
return parent::getName();
}
}
public function getURI(){
if(!is_null($this->getInput('f')) && !is_null($this->getInput('q'))) {
return self::URI
. 'search?filter_id='
. urlencode($this->getInput('f'))
. '&q='
. urlencode($this->getInput('q'));
} else {
return parent::getURI();
}
}
public function collectData(){
$queryJson = json_decode(getContents(
self::URI
. 'search.json?filter_id='
. urlencode($this->getInput('f'))
. '&q='
. urlencode($this->getInput('q'))
)) or returnServerError('Failed to query Derpibooru');
foreach($queryJson->search as $post) {
$item = array();
$postUri = self::URI . $post->id;
$item['uri'] = $postUri;
$item['title'] = $post->id;
$item['timestamp'] = strtotime($post->created_at);
$item['author'] = $post->uploader;
$item['enclosures'] = array('https:' . $post->image);
$item['categories'] = explode(', ', $post->tags);
$item['content'] = '<p><a href="' // image preview
. $postUri
. '"><img src="https:'
. $post->representations->medium
. '"></a></p><p>' // description
. $post->description
. '</p><p><b>Size:</b> ' // image size
. $post->width
. 'x'
. $post->height
. '<br><b>Source:</b> <a href="' // source link
. $post->source_url
. '">'
. $post->source_url
. '</a></p>';
$this->items[] = $item;
}
}
}

View File

@@ -15,6 +15,7 @@ class DesoutterBridge extends BridgeAbstract {
'news_lang' => array( 'news_lang' => array(
'name' => 'Language', 'name' => 'Language',
'type' => 'list', 'type' => 'list',
'required' => true,
'title' => 'Select your language', 'title' => 'Select your language',
'defaultValue' => 'Corporate', 'defaultValue' => 'Corporate',
'values' => array( 'values' => array(
@@ -65,6 +66,7 @@ class DesoutterBridge extends BridgeAbstract {
'industry_lang' => array( 'industry_lang' => array(
'name' => 'Language', 'name' => 'Language',
'type' => 'list', 'type' => 'list',
'required' => true,
'title' => 'Select your language', 'title' => 'Select your language',
'defaultValue' => 'Corporate', 'defaultValue' => 'Corporate',
'values' => array( 'values' => array(
@@ -115,13 +117,8 @@ class DesoutterBridge extends BridgeAbstract {
'full' => array( 'full' => array(
'name' => 'Load full articles', 'name' => 'Load full articles',
'type' => 'checkbox', 'type' => 'checkbox',
'required' => false,
'title' => 'Enable to load the full article for each item' 'title' => 'Enable to load the full article for each item'
),
'limit' => array(
'name' => 'Limit',
'type' => 'number',
'defaultValue' => 3,
'title' => "Maximum number of items to return in the feed.\n0 = unlimited"
) )
) )
); );
@@ -162,23 +159,19 @@ class DesoutterBridge extends BridgeAbstract {
$this->title = html_entity_decode($html->find('title', 0)->plaintext, ENT_QUOTES); $this->title = html_entity_decode($html->find('title', 0)->plaintext, ENT_QUOTES);
$limit = $this->getInput('limit') ?: 0;
foreach($html->find('article') as $article) { foreach($html->find('article') as $article) {
$item = array(); $item = array();
$item['uri'] = $article->find('a', 0)->href; $item['uri'] = $article->find('[itemprop="name"]', 0)->href;
$item['title'] = $article->find('a[title]', 0)->title; $item['title'] = $article->find('[itemprop="name"]', 0)->title;
if($this->getInput('full')) { if($this->getInput('full')) {
$item['content'] = $this->getFullNewsArticle($item['uri']); $item['content'] = $this->getFullNewsArticle($item['uri']);
} else { } else {
$item['content'] = $article->find('div.tile-body p', 0)->plaintext; $item['content'] = $article->find('[itemprop="description"]', 0)->plaintext;
} }
$this->items[] = $item; $this->items[] = $item;
if ($limit > 0 && count($this->items) >= $limit) break;
} }
} }
@@ -243,4 +236,5 @@ class DesoutterBridge extends BridgeAbstract {
echo $list; echo $list;
} }
} }

View File

@@ -22,7 +22,8 @@ class DevToBridge extends BridgeAbstract {
'name' => 'Full article', 'name' => 'Full article',
'type' => 'checkbox', 'type' => 'checkbox',
'required' => false, 'required' => false,
'title' => 'Enable to receive the full article for each item' 'title' => 'Enable to receive the full article for each item',
'defaultValue' => false
) )
) )
); );
@@ -45,22 +46,29 @@ apple-icon-5c6fa9f2bce280428589c6195b7f1924206a53b782b371cfe2d02da932c8c173.png'
} }
public function collectData() { public function collectData() {
$html = getSimpleHTMLDOMCached($this->getURI()) $html = getSimpleHTMLDOMCached($this->getURI())
or returnServerError('Could not request ' . $this->getURI()); or returnServerError('Could not request ' . $this->getURI());
$html = defaultLinkTo($html, static::URI); $html = defaultLinkTo($html, static::URI);
$articles = $html->find('div.crayons-story') $articles = $html->find('div[class="single-article"]')
or returnServerError('Could not find articles!'); or returnServerError('Could not find articles!');
foreach($articles as $article) { foreach($articles as $article) {
if($article->find('[class*="cta"]', 0)) { // Skip ads
continue;
}
$item = array(); $item = array();
$item['uri'] = $article->find('a[id*=article-link]', 0)->href; $item['uri'] = $article->find('a[id*=article-link]', 0)->href;
$item['title'] = $article->find('h2 > a', 0)->plaintext; $item['title'] = $article->find('h3', 0)->plaintext;
$item['timestamp'] = $article->find('time', 0)->datetime; // i.e. "Charlie Harrington・Sep 21"
$item['author'] = $article->find('a.crayons-story__secondary.fw-medium', 0)->plaintext; $item['timestamp'] = strtotime(explode('・', $article->find('h4 a', 0)->plaintext, 2)[1]);
$item['author'] = explode('・', $article->find('h4 a', 0)->plaintext, 2)[0];
// Profile image // Profile image
$item['enclosures'] = array($article->find('img', 0)->src); $item['enclosures'] = array($article->find('img', 0)->src);
@@ -68,6 +76,7 @@ apple-icon-5c6fa9f2bce280428589c6195b7f1924206a53b782b371cfe2d02da932c8c173.png'
if($this->getInput('full')) { if($this->getInput('full')) {
$fullArticle = $this->getFullArticle($item['uri']); $fullArticle = $this->getFullArticle($item['uri']);
$item['content'] = <<<EOD $item['content'] = <<<EOD
<img src="{$item['enclosures'][0]}" alt="{$item['author']}">
<p>{$fullArticle}</p> <p>{$fullArticle}</p>
EOD; EOD;
} else { } else {
@@ -77,21 +86,11 @@ EOD;
EOD; EOD;
} }
// categories $item['categories'] = array_map(function($e){ return $e->plaintext; }, $article->find('div.tags span.tag'));
foreach ($article->find('a.crayons-tag') as $tag) {
$item['categories'][] = str_replace('#', '', $tag->plaintext);
}
$this->items[] = $item; $this->items[] = $item;
} }
}
public function getName() {
if (!is_null($this->getInput('tag'))) {
return ucfirst($this->getInput('tag')) . ' - dev.to';
}
return parent::getName();
} }
private function getFullArticle($url) { private function getFullArticle($url) {
@@ -100,10 +99,7 @@ EOD;
$html = defaultLinkTo($html, static::URI); $html = defaultLinkTo($html, static::URI);
if ($html->find('div.crayons-article__cover', 0)) {
return $html->find('div.crayons-article__cover', 0) . $html->find('[id="article-body"]', 0);
}
return $html->find('[id="article-body"]', 0); return $html->find('[id="article-body"]', 0);
} }
} }

View File

@@ -1,84 +0,0 @@
<?php
class DiarioDeNoticiasBridge extends BridgeAbstract {
const NAME = 'Diário de Notícias (PT)';
const URI = 'https://dn.pt';
const DESCRIPTION = 'Diário de Notícias (DN.PT)';
const MAINTAINER = 'somini';
const PARAMETERS = array(
'Tag' => array(
'n' => array(
'name' => 'Tag Name',
'exampleValue' => 'rogerio-casanova',
)
)
);
const MONPT = array(
'jan',
'fev',
'mar',
'abr',
'mai',
'jun',
'jul',
'ago',
'set',
'out',
'nov',
'dez',
);
public function getIcon() {
return 'https://static.globalnoticias.pt/dn/common/images/favicons/favicon-128.png';
}
public function getName() {
switch($this->queriedContext) {
case 'Tag':
$name = self::NAME . ' | Tag | ' . $this->getInput('n');
break;
default:
$name = self::NAME;
}
return $name;
}
public function getURI() {
switch($this->queriedContext) {
case 'Tag':
$url = self::URI . '/tag/' . $this->getInput('n') . '.html';
break;
default:
$url = self::URI;
}
return $url;
}
public function collectData() {
$archives = self::getURI();
$html = getSimpleHTMLDOMCached($archives)
or returnServerError('Could not load content');
foreach($html->find('article') as $element) {
$item = array();
$title = $element->find('.t-am-title', 0);
$link = $element->find('a.t-am-text', 0);
$item['title'] = $title->plaintext;
$item['uri'] = self::URI . $link->href;
$snippet = $element->find('.t-am-lead', 0);
if ($snippet) {
$item['content'] = $snippet->plaintext;
}
preg_match('|edicao-do-dia\\/(?P<day>\d\d)-(?P<monpt>\w\w\w)-(?P<year>\d\d\d\d)|', $link->href, $d);
if ($d) {
$item['timestamp'] = sprintf('%s-%s-%s', $d['year'], array_search($d['monpt'], self::MONPT) + 1, $d['day']);
}
$this->items[] = $item;
}
}
}

View File

@@ -1,60 +0,0 @@
<?php
class DiarioDoAlentejoBridge extends BridgeAbstract {
const MAINTAINER = 'somini';
const NAME = 'Diário do Alentejo';
const URI = 'https://www.diariodoalentejo.pt';
const DESCRIPTION = 'Semanário Regionalista Independente';
const CACHE_TIMEOUT = 28800; // 8h
/* This is used to hack around obtaining a timestamp. It's just a list of Month names in Portuguese ... */
const PT_MONTH_NAMES = array(
'janeiro',
'fevereiro',
'março',
'abril',
'maio',
'junho',
'julho',
'agosto',
'setembro',
'outubro',
'novembro',
'dezembro');
public function getIcon() {
return 'https://www.diariodoalentejo.pt/images/favicon/apple-touch-icon.png';
}
public function collectData(){
/* This is slow as molasses (>30s!), keep the cache timeout high to avoid killing the host */
$html = getSimpleHTMLDOMCached($this->getURI() . '/pt/noticias-listagem.aspx')
or returnServerError('Could not load content');
foreach($html->find('.list_news .item') as $element) {
$item = array();
$item_link = $element->find('.body h2.title a', 0);
/* Another broken URL, see also `bridges/ComboiosDePortugalBridge.php` */
$item['uri'] = self::URI . implode('/', array_map('urlencode', explode('/', $item_link->href)));
$item['title'] = $item_link->innertext;
$item['timestamp'] = str_ireplace(
array_map(function($name) { return ' ' . $name . ' '; }, self::PT_MONTH_NAMES),
array_map(function($num) { return sprintf('-%02d-', $num); }, range(1, sizeof(self::PT_MONTH_NAMES))),
$element->find('span.date', 0)->innertext);
/* Fix the Image URL */
$item_image = $element->find('img.thumb', 0);
$item_image->src = preg_replace('/.*&img=([^&]+).*/', '\1', $item_image->getAttribute('data-src'));
/* Content: */
/* - Image */
/* - Category */
$content = $item_image .
'<center>' . $element->find('a.category', 0) . '</center>';
$item['content'] = defaultLinkTo($content, self::URI);
$this->items[] = $item;
}
}
}

View File

@@ -3,7 +3,7 @@ class DilbertBridge extends BridgeAbstract {
const MAINTAINER = 'kranack'; const MAINTAINER = 'kranack';
const NAME = 'Dilbert Daily Strip'; const NAME = 'Dilbert Daily Strip';
const URI = 'https://dilbert.com'; const URI = 'http://dilbert.com';
const CACHE_TIMEOUT = 21600; // 6h const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'The Unofficial Dilbert Daily Comic Strip'; const DESCRIPTION = 'The Unofficial Dilbert Daily Comic Strip';
@@ -17,9 +17,9 @@ class DilbertBridge extends BridgeAbstract {
$img = $element->find('img', 0); $img = $element->find('img', 0);
$link = $element->find('a', 0); $link = $element->find('a', 0);
$comic = $img->src; $comic = $img->src;
$title = $img->alt; $title = $link->alt;
$url = $link->href; $url = $link->href;
$date = substr(strrchr($url, '/'), 1); $date = substr($url, 25);
if (empty($title)) if (empty($title))
$title = 'Dilbert Comic Strip on ' . $date; $title = 'Dilbert Comic Strip on ' . $date;
$date = strtotime($date); $date = strtotime($date);

View File

@@ -62,11 +62,7 @@ class DiscogsBridge extends BridgeAbstract {
$item['id'] = $release['id']; $item['id'] = $release['id'];
$resId = array_key_exists('main_release', $release) ? $release['main_release'] : $release['id']; $resId = array_key_exists('main_release', $release) ? $release['main_release'] : $release['id'];
$item['uri'] = self::URI . $this->getInput('artistid') . '/release/' . $resId; $item['uri'] = self::URI . $this->getInput('artistid') . '/release/' . $resId;
if(isset($release['year'])) {
$item['timestamp'] = DateTime::createFromFormat('Y', $release['year'])->getTimestamp(); $item['timestamp'] = DateTime::createFromFormat('Y', $release['year'])->getTimestamp();
}
$item['content'] = $item['author'] . ' - ' . $item['title']; $item['content'] = $item['author'] . ' - ' . $item['title'];
$this->items[] = $item; $this->items[] = $item;
} }

View File

@@ -1,166 +0,0 @@
<?php
class DockerHubBridge extends BridgeAbstract {
const NAME = 'Docker Hub Bridge';
const URI = 'https://hub.docker.com';
const DESCRIPTION = 'Returns new images for a container';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(
'User Submitted Image' => array(
'user' => array(
'name' => 'User',
'type' => 'text',
'required' => true,
'exampleValue' => 'rssbridge',
),
'repo' => array(
'name' => 'Repository',
'type' => 'text',
'required' => true,
'exampleValue' => 'rss-bridge',
)
),
'Official Image' => array(
'repo' => array(
'name' => 'Repository',
'type' => 'text',
'required' => true,
'exampleValue' => 'postgres',
)
),
);
const CACHE_TIMEOUT = 3600; // 1 hour
private $apiURL = 'https://hub.docker.com/v2/repositories/';
private $imageUrlRegex = '/hub\.docker\.com\/r\/([\w]+)\/([\w-]+)\/?/';
private $officialImageUrlRegex = '/hub\.docker\.com\/_\/([\w-]+)\/?/';
public function detectParameters($url) {
$params = array();
// user submitted image
if(preg_match($this->imageUrlRegex, $url, $matches)) {
$params['context'] = 'User Submitted Image';
$params['user'] = $matches[1];
$params['repo'] = $matches[2];
return $params;
}
// official image
if(preg_match($this->officialImageUrlRegex, $url, $matches)) {
$params['context'] = 'Official Image';
$params['repo'] = $matches[1];
return $params;
}
return null;
}
public function collectData() {
$json = getContents($this->getApiUrl())
or returnServerError('Could not request: ' . $this->getURI());
$data = json_decode($json, false);
foreach ($data->results as $result) {
$item = array();
$lastPushed = date('Y-m-d H:i:s', strtotime($result->tag_last_pushed));
$item['title'] = $result->name;
$item['uid'] = $result->id;
$item['uri'] = $this->getTagUrl($result->name);
$item['author'] = $result->last_updater_username;
$item['timestamp'] = $result->tag_last_pushed;
$item['content'] = <<<EOD
<Strong>Tag</strong><br>
<p>{$result->name}</p>
<Strong>Last pushed</strong><br>
<p>{$lastPushed}</p>
<Strong>Images</strong><br>
{$this->getImages($result)}
EOD;
$this->items[] = $item;
}
}
public function getURI() {
if ($this->queriedContext === 'Official Image') {
return self::URI . '/_/' . $this->getRepo();
}
if ($this->getInput('repo')) {
return self::URI . '/r/' . $this->getRepo();
}
return parent::getURI();
}
public function getName() {
if ($this->getInput('repo')) {
return $this->getRepo() . ' - Docker Hub';
}
return parent::getName();
}
private function getRepo() {
if ($this->queriedContext === 'Official Image') {
return $this->getInput('repo');
}
return $this->getInput('user') . '/' . $this->getInput('repo');
}
private function getApiUrl() {
if ($this->queriedContext === 'Official Image') {
return $this->apiURL . 'library/' . $this->getRepo() . '/tags/?page_size=25&page=1';
}
return $this->apiURL . $this->getRepo() . '/tags/?page_size=25&page=1';
}
private function getLayerUrl($name, $digest) {
if ($this->queriedContext === 'Official Image') {
return self::URI . '/layers/' . $this->getRepo() . '/library/' .
$this->getRepo() . '/' . $name . '/images/' . $digest;
}
return self::URI . '/layers/' . $this->getRepo() . '/' . $name . '/images/' . $digest;
}
private function getTagUrl($name) {
if ($this->queriedContext === 'Official Image') {
return self::URI . '/_/' . $this->getRepo() . '?tab=tags&name=' . $name;
}
return self::URI . '/r/' . $this->getRepo() . '/tags?name=' . $name;
}
private function getImages($result) {
$html = <<<EOD
<table style="width:300px;"><thead><tr><th>Digest</th><th>OS/architecture</th></tr></thead></tbody>
EOD;
foreach ($result->images as $image) {
$layersUrl = $this->getLayerUrl($result->name, $image->digest);
$id = $this->getShortDigestId($image->digest);
$html .= <<<EOD
<tr>
<td><a href="{$layersUrl}">{$id}</a></td>
<td>{$image->os}/{$image->architecture}</td>
</tr>
EOD;
}
return $html . '</tbody></table>';
}
private function getShortDigestId($digest) {
$parts = explode(':', $digest);
return substr($parts[1], 0, 12);
}
}

View File

@@ -0,0 +1,9 @@
<?php
require_once('Shimmie2Bridge.php');
class DollbooruBridge extends Shimmie2Bridge {
const MAINTAINER = 'mitsukarenai';
const NAME = 'Dollbooru';
const URI = 'http://dollbooru.org/';
const DESCRIPTION = 'Returns images from given page';
}

View File

@@ -1,123 +0,0 @@
<?php
/**
* Retourne les dons d'une recherche filtrée sur le site Donnons.org
* Example: https://donnons.org/Sport/Ile-de-France
*/
class DonnonsBridge extends BridgeAbstract {
const MAINTAINER = 'Binnette';
const NAME = 'Donnons.org';
const URI = 'https://donnons.org';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = 'Retourne les dons depuis le site Donnons.org.';
const PARAMETERS = array(
array(
'q' => array(
'name' => 'Url de recherche',
'required' => true,
'exampleValue' => '/Sport/Ile-de-France',
'pattern' => '\/.*',
'title' => 'Faites une recherche sur le site. Puis copiez ici la fin de lurl. Doit commencer par /',
),
'p' => array(
'name' => 'Nombre de pages à scanner',
'type' => 'number',
'defaultValue' => 5,
'title' => 'Indique le nombre de pages de donnons.org qui seront scannées'
)
)
);
public function collectData() {
$pages = $this->getInput('p');
for($i = 1; $i <= $pages; $i++) {
$this->collectDataByPage($i);
}
}
private function collectDataByPage($page) {
$uri = $this->getPageURI($page);
$html = getSimpleHTMLDOM($uri)
or returnServerError('No results for this query.');
$searchDiv = $html->find('div[id=search]', 0);
if(!is_null($searchDiv)) {
$elements = $searchDiv->find('a.lst-annonce');
foreach($elements as $element) {
$item = array();
// Lien vers le don
$item['uri'] = self::URI . $element->href;
// Id de l'objet
$item['uid'] = $element->getAttribute('data-id');
// Grab info from json
$jsonString = $element->find('script', 0)->innertext;
$json = json_decode($jsonString, true);
$name = $json['name'];
$category = $json['category'];
$date = $json['availabilityStarts'];
$description = $json['description'];
$city = $json['availableAtOrFrom']['address']['addressLocality'];
$region = $json['availableAtOrFrom']['address']['addressRegion'];
// Grab info from HTML
$imageSrc = $element->find('img.ima-center', 0)->getAttribute('data-src');
$image = self::URI . $imageSrc;
$author = $element->find('div.avatar-holder', 0)->plaintext;
$content = '
<img style="margin-right:1em;" src="' . $image . '">
<div>
<h1>' . $name . '</h1>
<p>' . $description . '</p>
<p>Lieu : <b>' . $city . '</b> - ' . $region . '</p>
<p>Par : ' . $author . '</p>
<p>Date : ' . $date . '</p>
</div>
';
// Titre du don
$item['title'] = '[' . $category . '] ' . $name;
$item['timestamp'] = $date;
$item['author'] = $author;
$item['content'] = $content;
$item['enclosures'] = array($image);
$this->items[] = $item;
}
}
}
private function getPageURI($page) {
$uri = $this->getURI();
$haveQueryParams = strpos($uri, '?') !== false;
if($haveQueryParams) {
return $uri . '&page=' . $page;
} else {
return $uri . '?page=' . $page;
}
}
public function getURI() {
if(!is_null($this->getInput('q'))) {
return self::URI . $this->getInput('q');
}
return parent::getURI();
}
public function getName() {
if(!is_null($this->getInput('q'))) {
return 'Donnons.org - ' . $this->getInput('q');
}
return parent::getName();
}
}

View File

@@ -1,194 +0,0 @@
<?php
class DownDetectorBridge extends BridgeAbstract {
const MAINTAINER = 'teromene';
const NAME = 'DownDetector Bridge';
const URI = 'https://downdetector.com/';
const DESCRIPTION = 'Returns most recent downtimes from DownDetector';
const CACHE_TIMEOUT = 300; // 5 min
const PARAMETERS = array(
'All Websites' => array(
'country' => array(
'type' => 'list',
'name' => 'Country',
'values' => array(
'Argentina' => 'https://downdetector.com.ar',
'Australia' => 'https://downdetector.com.au',
'België' => 'https://allestoringen.be',
'Brasil' => 'https://downdetector.com.br',
'Canada' => 'https://downdetector.ca',
'Chile' => 'https://downdetector.cl',
'Colombia' => 'https://downdetector.com.co',
'Danmark' => 'https://downdetector.dk',
'Deutschland' => 'https://allestörungen.de',
'Ecuador' => 'https://downdetector.ec',
'España' => 'https://downdetector.es',
'France' => 'https://downdetector.fr',
'Hong Kong' => 'https://downdetector.hk',
'Hrvatska' => 'https://downdetector.hr',
'India' => 'https://downdetector.in',
'Indonesia' => 'https://downdetector.id',
'Ireland' => 'https://downdetector.ie',
'Italia' => 'https://downdetector.it',
'Magyarország' => 'https://downdetector.hu',
'Malaysia' => 'https://downdetector.my',
'México' => 'https://downdetector.mx',
'Nederland' => 'https://allestoringen.nl',
'New Zealand' => 'https://downdetector.co.nz',
'Norge' => 'https://downdetector.no',
'Pakistan' => 'https://downdetector.pk',
'Perú' => 'https://downdetector.pe',
'Pilipinas' => 'https://downdetector.ph',
'Polska' => 'https://downdetector.pl',
'Portugal' => 'https://downdetector.pt',
'România' => 'https://downdetector.ro',
'Schweiz' => 'https://allestörungen.ch',
'Singapore' => 'https://downdetector.sg',
'Slovensko' => 'https://downdetector.sk',
'South Africa' => 'https://downdetector.co.za',
'Suomi' => 'https://downdetector.fi',
'Sverige' => 'https://downdetector.se',
'Türkiye' => 'https://downdetector.web.tr',
'UAE' => 'https://downdetector.ae',
'UK' => 'https://downdetector.co.uk',
'United States' => 'https://downdetector.com',
'Österreich' => 'https://allestörungen.at',
'Česko' => 'https://downdetector.cz',
'Ελλάς' => 'https://downdetector.gr',
'Россия' => 'https://downdetector.ru',
'日本' => 'https://downdetector.jp'
)
)
),
'Specific Website' => array(
'page' => array(
'type' => 'text',
'name' => 'Status page',
'required' => true,
'exampleValue' => 'https://downdetector.com/status/rainbow-six',
'title' => 'URL of a DownDetector status page e.g: https://downdetector.com/status/rainbow-six/',
)
),
);
private $hostname = '';
private $statusPageId = '';
private $feedname = '';
private $statusUrlRegex = '/\/([a-zA-z0-9ö.]+)\/(?:statu(?:s|t)|problemas?|nu-merge
|(?:feil-)?problem(y|i)?(?:-storningar)?(?:-fejl)?|stoerung|durum|storing|fora-do-ar|ne-rabotaet
|masalah|shougai|ei-toimi)\/([a-zA-Z0-9-]+)/';
public function collectData(){
if ($this->queriedContext == 'Specific Website') {
preg_match($this->statusUrlRegex, $this->getInput('page'), $match)
or returnClientError('Given URL does not seem to at a DownDetector status page!');
$this->hostname = $match[1];
$this->statusPageId = $match[3];
}
$html = getSimpleHTMLDOM($this->getURI() . '/archive/')
or returnClientError('Could not request website!.');
$html = defaultLinkTo($html, $this->getURI());
if ($this->getInput('page')) {
$this->feedname = $html->find('li.breadcrumb-item.active', 0)->plaintext;
}
$table = $html->find('table.table-striped', 0);
if ($table) {
foreach ($table->find('tr') as $event) {
$td = $event->find('td', 0);
if (is_null($td)) {
continue;
}
$item['uri'] = $event->find('td', 0)->find('a', 0)->href;
$item['title'] = $event->find('td', 0)->find('a', 0)->plaintext .
'(' . trim($event->find('td', 1)->plaintext) . ' ' . trim($event->find('td', 2)->plaintext) . ')';
$item['content'] = 'User reports indicate problems at' . $event->find('td', 0)->find('a', 0)->plaintext .
' since ' . $event->find('td', 2)->plaintext;
$item['timestamp'] = $this->formatDate(
trim($event->find('td', 1)->plaintext),
trim($event->find('td', 2)->plaintext)
);
$this->items[] = $item;
}
}
}
public function getURI() {
if($this->getInput('country')) {
return $this->getInput('country');
}
if ($this->getInput('page')) {
return 'https://' . $this->hostname . '/status/' . $this->statusPageId;
}
return self::URI;
}
public function getName() {
if($this->getInput('country')) {
$country = $this->getCountry($this->getInput('country'));
return $country . ' - DownDetector';
}
if ($this->getInput('page')) {
$country = $this->getCountry($this->hostname);
return $this->feedname . ' - ' . $country . ' - DownDetector';
}
return self::NAME;
}
private function formatDate($date, $time) {
switch($this->getCountry()) {
case 'Australia':
case 'UK':
$date = DateTime::createFromFormat('d/m/Y', $date);
return $date->format('Y-m-d') . $time;
case 'Brasil':
case 'Chile':
case 'Colombia':
case 'Ecuador':
case 'España':
case 'Italia':
case 'Perú':
case 'Portugal':
$date = DateTime::createFromFormat('d/m/Y', $date);
return $date->format('Y-m-d') . $time;
case 'Magyarország':
$date = DateTime::createFromFormat('Y.m.d.', $date);
return $date->format('Y-m-d') . $time;
default:
return $date . $time;
}
}
private function getCountry() {
if($this->getInput('country')) {
$input = $this->getInput('country');
}
if ($this->getInput('page')) {
if (empty($this->hostname)) {
return 'N/A';
}
$input = 'https://' . $this->hostname;
}
$parameters = $this->getParameters();
$countryValues = array_flip($parameters['All Websites']['country']['values']);
$country = $countryValues[$input];
return $country;
}
}

View File

@@ -13,51 +13,48 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
} }
public function collectData(){ public function collectData(){
$html = getSimpleHTMLDOM(self::URI) $html = getSimpleHTMLDOM(self::URI . '/shots')
or returnServerError('Error while downloading the website content'); or returnServerError('Error while downloading the website content');
$json = $this->loadEmbeddedJsonData($html); $json = $this->loadEmbeddedJsonData($html);
foreach($html->find('li[id^="screenshot-"]') as $shot) { foreach($html->find('li[id^="screenshot-"]') as $shot) {
$item = array(); $item = [];
$additional_data = $this->findJsonForShot($shot, $json); $additional_data = $this->findJsonForShot($shot, $json);
if ($additional_data === null) { if ($additional_data === null) {
$item['uri'] = self::URI . $shot->find('a', 0)->href; $item['uri'] = self::URI . $shot->find('a', 0)->href;
$item['title'] = $shot->find('.shot-title', 0)->plaintext; $item['title'] = $shot->find('.dribbble-over strong', 0)->plaintext;
} else { } else {
$item['timestamp'] = strtotime($additional_data['published_at']); $item['timestamp'] = strtotime($additional_data['published_at']);
$item['uri'] = self::URI . $additional_data['path']; $item['uri'] = self::URI . $additional_data['path'];
$item['title'] = $additional_data['title']; $item['title'] = $additional_data['title'];
} }
$item['author'] = trim($shot->find('.user-information .display-name', 0)->plaintext); $item['author'] = trim($shot->find('.attribution-user a', 0)->plaintext);
$description = $shot->find('.comment', 0); $description = $shot->find('.comment', 0);
$item['content'] = $description === null ? '' : $description->plaintext; $item['content'] = $description === null ? '' : $description->plaintext;
$preview_path = $shot->find('figure img', 1)->attr['data-srcset']; $preview_path = $shot->find('picture source', 0)->attr['srcset'];
$item['content'] .= $this->getImageTag($preview_path, $item['title']); $item['content'] .= $this->getImageTag($preview_path, $item['title']);
$item['enclosures'] = array($this->getFullSizeImagePath($preview_path)); $item['enclosures'] = [$this->getFullSizeImagePath($preview_path)];
$this->items[] = $item; $this->items[] = $item;
} }
} }
private function loadEmbeddedJsonData($html){ private function loadEmbeddedJsonData($html){
$json = array(); $json = [];
$scripts = $html->find('script'); $scripts = $html->find('script');
foreach($scripts as $script) { foreach($scripts as $script) {
if(strpos($script->innertext, 'newestShots') !== false) { if(strpos($script->innertext, 'newestShots') !== false) {
// fix single quotes // fix single quotes
$script->innertext = preg_replace('/\'(.*)\'(,?)$/im', '"\1"\2', $script->innertext); $script->innertext = str_replace('\'', '"', $script->innertext);
// fix JavaScript JSON (why do they not adhere to the standard?) // fix JavaScript JSON (why do they not adhere to the standard?)
$script->innertext = preg_replace('/^(\s*)(\w+):/im', '\1"\2":', $script->innertext); $script->innertext = preg_replace('/(\w+):/i', '"\1":', $script->innertext);
// fix relative dates, so they are recognized by strtotime
$script->innertext = preg_replace('/"about ([0-9]+ hours? ago)"(,?)$/im', '"\1"\2', $script->innertext);
// find beginning of JSON array // find beginning of JSON array
$start = strpos($script->innertext, '['); $start = strpos($script->innertext, '[');
@@ -86,7 +83,7 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
private function getImageTag($preview_path, $title){ private function getImageTag($preview_path, $title){
return sprintf( return sprintf(
'<br /> <a href="%s"><img srcset="%s" alt="%s" /></a>', '<br /> <a href="%s"><img src="%s" alt="%s" /></a>',
$this->getFullSizeImagePath($preview_path), $this->getFullSizeImagePath($preview_path),
$preview_path, $preview_path,
$title $title
@@ -94,11 +91,6 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
} }
private function getFullSizeImagePath($preview_path){ private function getFullSizeImagePath($preview_path){
// Get last image from srcset return str_replace('_1x', '', $preview_path);
$src_set_urls = explode(',', $preview_path);
$url = end($src_set_urls);
$url = explode(' ', $url)[1];
return htmlspecialchars_decode($url);
} }
} }

View File

@@ -1,70 +0,0 @@
<?php
class EconomistBridge extends BridgeAbstract {
const NAME = 'The Economist: Latest Updates';
const URI = 'https://www.economist.com';
const DESCRIPTION = 'Fetches the latest updates from the Economist.';
const MAINTAINER = 'thefranke';
const CACHE_TIMEOUT = 3600; // 1h
public function getIcon() {
return 'https://www.economist.com/sites/default/files/econfinal_favicon.ico';
}
public function collectData() {
$html = getSimpleHTMLDOM(self::URI . '/latest/')
or returnServerError('Could not fetch latest updates form The Economist.');
foreach($html->find('div.teaser') as $element) {
$a = $element->find('a.headline-link', 0);
$href = $a->href;
if (substr($href, 0, 4) != 'http')
$href = self::URI . $a->href;
$full = getSimpleHTMLDOMCached($href);
$article = $full->find('article', 0);
$header = $article->find('span[itemprop="headline"]', 0);
$headerimg = $article->find('div[itemprop="image"]', 0)->find('img', 0);
$author = $article->find('p[itemprop="byline"]', 0);
$time = $article->find('time', 0);
$content = $article->find('div[itemprop="text"]', 0);
$section = array( $article->find('strong[itemprop="articleSection"]', 0)->plaintext );
// Author
if ($author)
$author = substr($author->innertext, 3, strlen($author));
else
$author = 'The Economist';
// 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 = '';
$item = array();
$item['title'] = $header->innertext;
$item['uri'] = $href;
$item['timestamp'] = strtotime($time->datetime);
$item['author'] = $author;
$item['categories'] = $section;
$item['content'] = '<img style="max-width: 100%" src="'
. $headerimg->src . '">' . $content->innertext;
$this->items[] = $item;
if (count($this->items) >= 10)
break;
}
}
}

View File

@@ -6,36 +6,25 @@ class EliteDangerousGalnetBridge extends BridgeAbstract {
const URI = 'https://community.elitedangerous.com/galnet/'; const URI = 'https://community.elitedangerous.com/galnet/';
const CACHE_TIMEOUT = 7200; // 2h const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Returns the latest page of news from Galnet'; const DESCRIPTION = 'Returns the latest page of news from Galnet';
const PARAMETERS = array(
array( public function getIcon() {
'language' => array( return 'https://community.elitedangerous.com/sites/
'name' => 'Language', EDSITE_COMM/themes/bootstrap/bootstrap_community/favicon.ico';
'type' => 'list', }
'values' => array(
'English' => 'en',
'French' => 'fr',
'German' => 'de'
),
'defaultValue' => 'en'
)
)
);
public function collectData(){ public function collectData(){
$language = $this->getInput('language'); $html = getSimpleHTMLDOM(self::URI)
$url = 'https://community.elitedangerous.com/';
$url = $url . $language . '/galnet';
$html = getSimpleHTMLDOM($url)
or returnServerError('Error while downloading the website content'); or returnServerError('Error while downloading the website content');
foreach($html->find('div.article') as $element) { foreach($html->find('div.article') as $element) {
$item = array(); $item = array();
$uri = $element->find('h3 a', 0)->href; $uri = $element->find('h3 a', 0)->href;
$uri = 'https://community.elitedangerous.com/' . $language . $uri; $uri = self::URI . substr($uri, strlen('/galnet/'));
$item['uri'] = $uri; $item['uri'] = $uri;
$item['title'] = $element->find('h3 a', 0)->plaintext; $title = $element->find('h3 a', 0)->plaintext;
$item['title'] = substr($title, 1); //remove the space between icon and title
$content = $element->find('p', -1)->innertext; $content = $element->find('p', -1)->innertext;
$item['content'] = $content; $item['content'] = $content;
@@ -47,8 +36,5 @@ class EliteDangerousGalnetBridge extends BridgeAbstract {
$this->items[] = $item; $this->items[] = $item;
} }
//Remove duplicates that sometimes show up on the website
$this->items = array_unique($this->items, SORT_REGULAR);
} }
} }

View File

@@ -95,7 +95,7 @@ class ElloBridge extends BridgeAbstract {
private function getEnclosures($post, $postData) { private function getEnclosures($post, $postData) {
$assets = array(); $assets = [];
foreach($post->links->assets as $asset) { foreach($post->links->assets as $asset) {
foreach($postData->linked->assets as $assetLink) { foreach($postData->linked->assets as $assetLink) {
if($asset == $assetLink->id) { if($asset == $assetLink->id) {
@@ -120,11 +120,9 @@ class ElloBridge extends BridgeAbstract {
} }
private function getAPIKey() { private function getAPIKey() {
$cacheFac = new CacheFactory(); $cache = Cache::create('FileCache');
$cacheFac->setWorkingDir(PATH_LIB_CACHES); $cache->setPath(CACHE_DIR);
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); $cache->setParameters(['key']);
$cache->setScope(get_called_class());
$cache->setKey(array('key'));
$key = $cache->loadData(); $key = $cache->loadData();
if($key == null) { if($key == null) {
@@ -145,4 +143,5 @@ class ElloBridge extends BridgeAbstract {
return parent::getName(); return parent::getName();
} }
} }

View File

@@ -3,7 +3,7 @@ class ElsevierBridge extends BridgeAbstract {
const MAINTAINER = 'Pierre Mazière'; const MAINTAINER = 'Pierre Mazière';
const NAME = 'Elsevier journals recent articles'; const NAME = 'Elsevier journals recent articles';
const URI = 'https://www.journals.elsevier.com/'; const URI = 'http://www.journals.elsevier.com/';
const CACHE_TIMEOUT = 43200; //12h const CACHE_TIMEOUT = 43200; //12h
const DESCRIPTION = 'Returns the recent articles published in Elsevier journals'; const DESCRIPTION = 'Returns the recent articles published in Elsevier journals';

View File

@@ -1,26 +0,0 @@
<?php
class EngadgetBridge extends FeedExpander {
const MAINTAINER = 'IceWreck';
const NAME = 'Engadget Bridge';
const URI = 'https://www.engadget.com/';
const CACHE_TIMEOUT = 3600;
const DESCRIPTION = 'Article content for Engadget.';
public function collectData(){
$this->collectExpandableDatas(static::URI . 'rss.xml', 15);
}
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
// $articlePage gets the entire page's contents
$articlePage = getSimpleHTMLDOM($newsItem->link);
// figure contain's the main article image
$article = $articlePage->find('figure', 0);
// .article-text has the actual article
foreach($articlePage->find('.article-text') as $element)
$article = $article . $element;
$item['content'] = $article;
return $item;
}
}

View File

@@ -1,93 +0,0 @@
<?php
class EpicgamesBridge extends BridgeAbstract {
const NAME = 'Epic Games Store News';
const MAINTAINER = 'otakuf';
const URI = 'https://www.epicgames.com';
const DESCRIPTION = 'Returns the latest posts from epicgames.com';
const CACHE_TIMEOUT = 3600; // 60min
const PARAMETERS = array( array(
'postcount' => array(
'name' => 'Limit',
'type' => 'number',
'title' => 'Maximum number of items to return',
'defaultValue' => 10,
),
'language' => array(
'name' => 'Language',
'type' => 'list',
'values' => array(
'English' => 'en',
'العربية' => 'ar',
'Deutsch' => 'de',
'Español (Spain)' => 'es-ES',
'Español (LA)' => 'es-MX',
'Français' => 'fr',
'Italiano' => 'it',
'日本語' => 'ja',
'한국어' => 'ko',
'Polski' => 'pl',
'Português (Brasil)' => 'pt-BR',
'Русский' => 'ru',
'ไทย' => 'th',
'Türkçe' => 'tr',
'简体中文' => 'zh-CN',
'繁體中文' => 'zh-Hant',
),
'title' => 'Language of blog posts',
'defaultValue' => 'en',
),
));
public function collectData() {
$api = 'https://store-content.ak.epicgames.com/api/';
// Get sticky posts first
// Example: https://store-content.ak.epicgames.com/api/ru/content/blog/sticky?locale=ru
$urlSticky = $api . $this->getInput('language') . '/content/blog/sticky';
// Then get posts
// Example: https://store-content.ak.epicgames.com/api/ru/content/blog?limit=25
$urlBlog = $api . $this->getInput('language') . '/content/blog?limit=' . $this->getInput('postcount');
$dataSticky = getContents($urlSticky)
or returnServerError('Unable to get the sticky posts from epicgames.com!');
$dataBlog = getContents($urlBlog)
or returnServerError('Unable to get the news posts from epicgames.com!');
// Merge data
$decodedData = array_merge(json_decode($dataSticky), json_decode($dataBlog));
foreach($decodedData as $key => $value) {
$item = array();
$item['uri'] = self::URI . $value->url;
$item['title'] = $value->title;
$item['timestamp'] = $value->date;
$item['author'] = 'Epic Games Store';
if(!empty($value->author)) {
$item['author'] = $value->author;
}
if(!empty($value->content)) {
$item['content'] = defaultLinkTo($value->content, self::URI);
}
if(!empty($value->image)) {
$item['enclosures'][] = $value->image;
}
$item['uid'] = $value->_id;
$item['id'] = $value->_id;
$this->items[] = $item;
}
// Sort data
usort($this->items, function ($item1, $item2) {
if ($item2['timestamp'] == $item1['timestamp']) {
return 0;
}
return ($item2['timestamp'] < $item1['timestamp']) ? -1 : 1;
});
// Limit data
$this->items = array_slice($this->items, 0, $this->getInput('postcount'));
}
}

View File

@@ -1,70 +0,0 @@
<?php
class EsquerdaNetBridge extends FeedExpander {
const MAINTAINER = 'somini';
const NAME = 'Esquerda.net';
const URI = 'https://www.esquerda.net';
const DESCRIPTION = 'Esquerda.net';
const PARAMETERS = array(
array(
'feed' => array(
'name' => 'Feed',
'type' => 'list',
'defaultValue' => 'Geral',
'values' => array(
'Geral' => 'geral',
'Dossier' => 'artigos-dossier',
'Vídeo' => 'video',
'Opinião' => 'opinioes',
'Rádio' => 'radio',
)
)
)
);
public function getURI() {
$type = $this->getInput('feed');
return self::URI . '/rss/' . $type;
}
public function getIcon() {
return 'https://www.esquerda.net/sites/default/files/favicon_0.ico';
}
public function collectData(){
parent::collectExpandableDatas($this->getURI());
}
protected function parseItem($newsItem){
# Fix Publish date
$badDate = $newsItem->pubDate;
preg_match('|(?P<day>\d\d)/(?P<month>\d\d)/(?P<year>\d\d\d\d) - (?P<hour>\d\d):(?P<minute>\d\d)|', $badDate, $d);
$newsItem->pubDate = sprintf('%s-%s-%sT%s:%s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['minute']);
$item = parent::parseItem($newsItem);
# Include all the content
$uri = $item['uri'];
$html = getSimpleHTMLDOMCached($uri)
or returnServerError('Could not load content for ' . $uri);
$content = $html->find('div#content div.content', 0);
## Fix author
$authorHTML = $html->find('.field-name-field-op-author a', 0);
if ($authorHTML) {
$item['author'] = $authorHTML->innertext;
$authorHTML->remove();
}
## Remove crap
$content->find('.field-name-addtoany', 0)->remove();
## Fix links
$content = defaultLinkTo($content, self::URI);
## Fix Images
foreach($content->find('img') as $img) {
$altSrc = $img->getAttribute('data-src');
if ($altSrc) {
$img->setAttribute('src', $altSrc);
}
$img->width = null;
$img->height = null;
}
$item['content'] = $content;
return $item;
}
}

View File

@@ -1,7 +1,7 @@
<?php <?php
class ExtremeDownloadBridge extends BridgeAbstract { class ExtremeDownloadBridge extends BridgeAbstract {
const NAME = 'Extreme Download'; const NAME = 'Extreme Download';
const URI = 'https://www.extreme-down.tv/'; const URI = 'https://ww1.extreme-d0wn.com/';
const DESCRIPTION = 'Suivi de série sur Extreme Download'; const DESCRIPTION = 'Suivi de série sur Extreme Download';
const MAINTAINER = 'sysadminstory'; const MAINTAINER = 'sysadminstory';
const PARAMETERS = array( const PARAMETERS = array(
@@ -15,6 +15,7 @@ class ExtremeDownloadBridge extends BridgeAbstract {
'filter' => array( 'filter' => array(
'name' => 'Type de contenu', 'name' => 'Type de contenu',
'type' => 'list', 'type' => 'list',
'required' => 'true',
'title' => 'Type de contenu à suivre : Téléchargement, Streaming ou les deux', 'title' => 'Type de contenu à suivre : Téléchargement, Streaming ou les deux',
'values' => array( 'values' => array(
'Streaming et Téléchargement' => 'both', 'Streaming et Téléchargement' => 'both',
@@ -81,16 +82,6 @@ class ExtremeDownloadBridge extends BridgeAbstract {
} }
} }
public function getURI() {
switch($this->queriedContext) {
case 'Suivre la publication des épisodes d\'une série en cours de diffusion':
return self::URI . $this->getInput('url');
break;
default:
return self::URI;
}
}
private function findLinkType($element) private function findLinkType($element)
{ {
$return = ''; $return = '';
@@ -109,4 +100,5 @@ class ExtremeDownloadBridge extends BridgeAbstract {
return $return; return $return;
} }
} }

View File

@@ -2,7 +2,7 @@
class FB2Bridge extends BridgeAbstract { class FB2Bridge extends BridgeAbstract {
const MAINTAINER = 'teromene'; const MAINTAINER = 'teromene';
const NAME = 'Facebook Bridge | Touch Site'; const NAME = 'Facebook Alternate';
const URI = 'https://www.facebook.com/'; const URI = 'https://www.facebook.com/';
const CACHE_TIMEOUT = 1000; const CACHE_TIMEOUT = 1000;
const DESCRIPTION = 'Input a page title or a profile log. For a profile log, const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
@@ -12,12 +12,7 @@ class FB2Bridge extends BridgeAbstract {
'u' => array( 'u' => array(
'name' => 'Username', 'name' => 'Username',
'required' => true 'required' => true
), )
'abbrev_name' => array(
'name' => 'Abbreviate author name in title',
'type' => 'checkbox',
'defaultValue' => true,
),
)); ));
public function getIcon() { public function getIcon() {
@@ -77,15 +72,15 @@ class FB2Bridge extends BridgeAbstract {
$pageInfo = $this->getPageInfos($page, $cookies); $pageInfo = $this->getPageInfos($page, $cookies);
if($pageInfo['userId'] === null) { if($pageInfo['userId'] === null) {
returnClientError(<<<EOD echo <<<EOD
Unable to get the page id. You should consider getting the ID by hand, then importing it into FB2Bridge Unable to get the page id. You should consider getting the ID by hand, then importing it into FB2Bridge
EOD EOD;
); die();
} elseif($pageInfo['userId'] == -1) { } elseif($pageInfo['userId'] == -1) {
returnClientError(<<<EOD echo <<<EOD
This page is not accessible without being logged in. This page is not accessible without being logged in.
EOD EOD;
); die();
} }
} }
@@ -100,19 +95,19 @@ EOD
foreach($html->find('article') as $content) { foreach($html->find('article') as $content) {
$item = array(); $item = array();
//echo $content; die();
preg_match('/publish_time\\\":([0-9]+),/', $content->getAttribute('data-store', 0), $match); preg_match('/publish_time\\\":([0-9]+),/', $content->getAttribute('data-store', 0), $match);
if(isset($match[1])) if(isset($match[1]))
$timestamp = $match[1]; $timestamp = $match[1];
else else
$timestamp = 0; $timestamp = 0;
$item['uri'] = html_entity_decode('https://touch.facebook.com' $item['uri'] = html_entity_decode('http://touch.facebook.com'
. $content->find("div[class='_52jc _5qc4 _78cz _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href'), ENT_QUOTES); . $content->find("div[class='_52jc _5qc4 _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href'), ENT_QUOTES);
//Decode images //Decode images
$imagecleaned = preg_replace_callback('/<i [^>]* style="[^"]*url\(\'(.*?)\'\).*?><\/i>/m', function ($matches) { $imagecleaned = preg_replace_callback('/<i [^>]* style="[^"]*url\(\'(.*?)\'\).*?><\/i>/m', function ($matches) {
return "<img src='" . str_replace(array('\\3a ', '\\3d ', '\\26 '), array(':', '=', '&'), $matches[1]) . "' />"; return "<img src='" . str_replace(['\\3a ', '\\3d ', '\\26 '], [':', '=', '&'], $matches[1]) . "' />";
}, $content); }, $content);
$content = str_get_html($imagecleaned); $content = str_get_html($imagecleaned);
@@ -164,11 +159,7 @@ EOD
$content = preg_replace('/<img src=\'.*?safe_image\.php.*?\' \/>/m', '', $content); $content = preg_replace('/<img src=\'.*?safe_image\.php.*?\' \/>/m', '', $content);
//Remove the double section tags //Remove the double section tags
$content = str_replace( $content = str_replace(['<section><section>', '</section></section>'], ['<section>', '</section>'], $content);
array('<section><section>', '</section></section>'),
array('<section>', '</section>'),
$content
);
//Move the section tag link upper, if it is down //Move the section tag link upper, if it is down
$content = str_get_html($content); $content = str_get_html($content);
@@ -191,10 +182,8 @@ EOD
$item['content'] = html_entity_decode($content, ENT_QUOTES); $item['content'] = html_entity_decode($content, ENT_QUOTES);
$title = $author; $title = $author;
if ($this->getInput('abbrev_name') === true) {
if (strlen($title) > 24) if (strlen($title) > 24)
$title = substr($title, 0, strpos(wordwrap($title, 24), "\n")) . '...'; $title = substr($title, 0, strpos(wordwrap($title, 24), "\n")) . '...';
}
$title = $title . ' | ' . strip_tags($content); $title = $title . ' | ' . strip_tags($content);
if (strlen($title) > 64) if (strlen($title) > 64)
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...'; $title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
@@ -209,6 +198,7 @@ EOD
} }
//Builds the HTML from the encoded JS that Facebook provides. //Builds the HTML from the encoded JS that Facebook provides.
private function buildContent($pageContent){ private function buildContent($pageContent){
// The html ends with: // The html ends with:
@@ -223,6 +213,7 @@ EOD
return str_get_html($htmlContent); return str_get_html($htmlContent);
} }
//Builds the cookie from the page, as Facebook sometimes refuses to give //Builds the cookie from the page, as Facebook sometimes refuses to give
//the page if no cookie is provided. //the page if no cookie is provided.
private function getCookies($pageURL){ private function getCookies($pageURL){
@@ -292,20 +283,11 @@ EOD
} }
public function getName(){ public function getName(){
$username = $this->getInput('u'); return (isset($this->name) ? $this->name . ' - ' : '') . 'Facebook Bridge';
if (isset($username)) {
return $this->getInput('u') . ' | Facebook';
} else {
return self::NAME;
}
} }
public function getURI(){ public function getURI(){
$username = $this->getInput('u'); return 'http://facebook.com';
if (isset($username)) {
return 'https://facebook.com/' . $this->getInput('u') . '/posts';
} else {
return self::URI;
}
} }
} }

View File

@@ -11,6 +11,7 @@ class FDroidBridge extends BridgeAbstract {
'u' => array( 'u' => array(
'name' => 'Widget selection', 'name' => 'Widget selection',
'type' => 'list', 'type' => 'list',
'required' => true,
'values' => array( 'values' => array(
'Latest added apps' => 'added', 'Latest added apps' => 'added',
'Latest updated apps' => 'updated' 'Latest updated apps' => 'updated'
@@ -28,14 +29,14 @@ class FDroidBridge extends BridgeAbstract {
or returnServerError('Could not request F-Droid.'); or returnServerError('Could not request F-Droid.');
// targetting the corresponding widget based on user selection // targetting the corresponding widget based on user selection
// "updated" is the 5th widget on the page, "added" is the 6th // "updated" is the 4th widget on the page, "added" is the 5th
switch($this->getInput('u')) { switch($this->getInput('u')) {
case 'updated': case 'updated':
$html_widget = $html->find('div.sidebar-widget', 5); $html_widget = $html->find('div.sidebar-widget', 4);
break; break;
default: default:
$html_widget = $html->find('div.sidebar-widget', 6); $html_widget = $html->find('div.sidebar-widget', 5);
break; break;
} }

View File

@@ -1,67 +0,0 @@
<?php
class FM4Bridge extends BridgeAbstract
{
const MAINTAINER = 'joni1993';
const NAME = 'FM4 Bridge';
const URI = 'https://fm4.orf.at';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = 'Feed for FM4 articles by tags (authors)';
const PARAMETERS = array(
array(
'tag' => array(
'name' => 'Tag (author, category, ...)',
'title' => 'Tag to retrieve',
'exampleValue' => 'musik'
),
'loadcontent' => array(
'name' => 'Load Full Article Content',
'title' => 'Retrieve full content of articles (may take longer)',
'type' => 'checkbox'
),
'pages' => array(
'name' => 'Pages',
'title' => 'Amount of pages to load',
'type' => 'number',
'defaultValue' => 1
)
)
);
private function getPageData($tag, $page) {
if($tag)
$uri = self::URI . '/tags/' . $tag;
else
$uri = self::URI;
$uri = $uri . '?page=' . $page;
$html = getSimpleHTMLDOM($uri)
or returnServerError('Error while downloading the website content');
$page_items = array();
foreach ($html->find('div[class*=listItem]') as $article) {
$item = array();
$item['uri'] = $article->find('a', 0)->href;
$item['title'] = $article->find('h2', 0)->plaintext;
$item['author'] = $article->find('p[class*=keyword]', 0)->plaintext;
$item['timestamp'] = strtotime($article->find('p[class*=time]', 0)->plaintext);
if ($this->getInput('loadcontent')) {
$item['content'] = getSimpleHTMLDOM($item['uri'])->find('div[class=storyText]', 0)->innertext
or returnServerError('Error while downloading the full article');
}
$page_items[] = $item;
}
return $page_items;
}
public function collectData() {
for ($cur_page = 1; $cur_page <= $this->getInput('pages'); $cur_page++) {
$this->items = array_merge($this->items, $this->getPageData($this->getInput('tag'), $cur_page));
}
}
}

View File

@@ -1,115 +0,0 @@
<?php
class FSecureBlogBridge extends BridgeAbstract {
const NAME = 'F-Secure Blog';
const URI = 'https://blog.f-secure.com';
const DESCRIPTION = 'F-Secure Blog';
const MAINTAINER = 'simon816';
const PARAMETERS = array(
'' => array(
'categories' => array(
'name' => 'Blog categories',
'exampleValue' => 'home-security',
),
'language' => array(
'name' => 'Language',
'defaultValue' => 'en',
),
'oldest_date' => array(
'name' => 'Oldest article date',
'exampleValue' => '-2 months',
),
)
);
public function getURI() {
$lang = $this->getInput('language') or 'en';
if ($lang === 'en') {
return self::URI;
}
return self::URI . "/$lang";
}
public function collectData() {
$this->items = array();
$this->seen = array();
$this->oldest = strtotime($this->getInput('oldest_date')) ?: 0;
$categories = $this->getInput('categories');
if (!empty($categories)) {
foreach (explode(',', $categories) as $cat) {
if (!empty($cat)) {
$this->collectCategory($cat);
}
}
return;
}
$html = getSimpleHTMLDOMCached($this->getURI() . '/');
foreach ($html->find('ul.c-header-menu-desktop__list li a') as $link) {
$url = parse_url($link->href);
if (($pos = strpos($url['path'], '/category/')) !== false) {
$cat = substr($url['path'], $pos + strlen('/category/'), -1);
$this->collectCategory($cat);
}
}
}
private function collectCategory($category) {
$url = $this->getURI() . "/category/$category/";
while ($url) {
$url = $this->collectListing($url);
}
}
// n.b. this relies on articles to be ordered by date so the cutoff works
private function collectListing($url) {
$html = getSimpleHTMLDOMCached($url, 60 * 60);
$items = $html->find('section.b-blog .l-blog__content__listing div.c-listing-item');
$catName = trim($html->find('section.b-blog .c-blog-header__title', 0)->plaintext);
foreach ($items as $item) {
$url = $item->getAttribute('data-url');
if (!$this->collectArticle($url)) {
return null; // Too old, stop collecting
}
}
// Point's to 404 for non-english blog
// $next = $html->find('link[rel=next]', 0);
$next = $html->find('ul.page-numbers a.next', 0);
return $next ? $next->href : null;
}
// Returns a boolean whether to continue collecting articles
// i.e. date is after oldest cutoff
private function collectArticle($url) {
if (array_key_exists($url, $this->seen)) {
return true;
}
$html = getSimpleHTMLDOMCached($url);
$rssItem = array( 'uri' => $url, 'uid' => $url );
$rssItem['title'] = $html->find('meta[property=og:title]', 0)->content;
$dt = $html->find('meta[property=article:published_time]', 0)->content;
// Exit if too old
if (strtotime($dt) < $this->oldest) {
return false;
}
$rssItem['timestamp'] = $dt;
$img = $html->find('meta[property=og:image]', 0);
$rssItem['enclosures'] = $img ? array($img->content) : array();
$rssItem['author'] = trim($html->find('.c-blog-author__text a', 0)->plaintext);
$rssItem['categories'] = array_map(function ($link) {
return trim($link->plaintext);
}, $html->find('.b-single-header__categories .c-category-list a'));
$rssItem['content'] = trim($html->find('article', 0)->innertext);
$this->items[] = $rssItem;
$this->seen[$url] = 1;
return true;
}
}

View File

@@ -1,36 +0,0 @@
<?php
class FabriceBellardBridge extends BridgeAbstract {
const NAME = 'Fabrice Bellard';
const URI = 'https://bellard.org/';
const DESCRIPTION = "Fabrice Bellard's Home Page";
const MAINTAINER = 'somini';
public function collectData() {
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not load content');
foreach ($html->find('p') as $obj) {
$item = array();
$html = defaultLinkTo($html, $this->getURI());
$links = $obj->find('a');
if (count($links) > 0) {
$link_uri = $links[0]->href;
} else {
$link_uri = $this->getURI();
}
/* try to make sure the link is valid */
if ($link_uri[-1] !== '/' && strpos($link_uri, '/') === false) {
$link_uri = $link_uri . '/';
}
$item['title'] = strip_tags($obj->innertext);
$item['uri'] = $link_uri;
$item['content'] = $obj->innertext;
$this->items[] = $item;
}
}
}

View File

@@ -2,7 +2,7 @@
class FacebookBridge extends BridgeAbstract { class FacebookBridge extends BridgeAbstract {
const MAINTAINER = 'teromene, logmanoriginal'; const MAINTAINER = 'teromene, logmanoriginal';
const NAME = 'Facebook Bridge | Main Site'; const NAME = 'Facebook Bridge';
const URI = 'https://www.facebook.com/'; const URI = 'https://www.facebook.com/';
const CACHE_TIMEOUT = 300; // 5min const CACHE_TIMEOUT = 300; // 5min
const DESCRIPTION = 'Input a page title or a profile log. For a profile log, const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
@@ -30,7 +30,7 @@ class FacebookBridge extends BridgeAbstract {
'type' => 'checkbox', 'type' => 'checkbox',
'required' => false, 'required' => false,
'defaultValue' => false, 'defaultValue' => false,
'title' => 'Feed includes reviews when unchecked' 'title' => 'Feed includes reviews when checked'
) )
), ),
'Group' => array( 'Group' => array(
@@ -66,13 +66,14 @@ class FacebookBridge extends BridgeAbstract {
case 'User': case 'User':
if(!empty($this->authorName)) { if(!empty($this->authorName)) {
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName; return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName
. ' - ' . static::NAME;
} }
break; break;
case 'Group': case 'Group':
if(!empty($this->groupName)) { if(!empty($this->groupName)) {
return $this->groupName; return $this->groupName . ' - ' . static::NAME;
} }
break; break;
@@ -81,34 +82,6 @@ class FacebookBridge extends BridgeAbstract {
return parent::getName(); return parent::getName();
} }
public function detectParameters($url){
$params = array();
// By profile
$regex = '/^(https?:\/\/)?(www\.)?facebook\.com\/profile\.php\?id\=([^\/?&\n]+)?(.*)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['u'] = urldecode($matches[3]);
return $params;
}
// By group
$regex = '/^(https?:\/\/)?(www\.)?facebook\.com\/groups\/([^\/?\n]+)?(.*)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['g'] = urldecode($matches[3]);
return $params;
}
// By username
$regex = '/^(https?:\/\/)?(www\.)?facebook\.com\/([^\/?\n]+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['u'] = urldecode($matches[3]);
return $params;
}
return null;
}
public function getURI() { public function getURI() {
$uri = self::URI; $uri = self::URI;
@@ -169,19 +142,9 @@ class FacebookBridge extends BridgeAbstract {
private function collectGroupData() { private function collectGroupData() {
if(getEnv('HTTP_ACCEPT_LANGUAGE')) { $header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n");
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE'));
} else {
$header = array();
}
$touchURI = str_replace( $html = getSimpleHTMLDOM($this->getURI(), $header)
'https://www.facebook',
'https://touch.facebook',
$this->getURI()
);
$html = getSimpleHTMLDOM($touchURI, $header)
or returnServerError('Failed loading facebook page: ' . $this->getURI()); or returnServerError('Failed loading facebook page: ' . $this->getURI());
if(!$this->isPublicGroup($html)) { if(!$this->isPublicGroup($html)) {
@@ -192,18 +155,19 @@ class FacebookBridge extends BridgeAbstract {
$this->groupName = $this->extractGroupName($html); $this->groupName = $this->extractGroupName($html);
$posts = $html->find('div.story_body_container') $posts = $html->find('div.userContentWrapper')
or returnServerError('Failed finding posts!'); or returnServerError('Failed finding posts!');
foreach($posts as $post) { foreach($posts as $post) {
$item = array(); $item = array();
$item['uri'] = $this->extractGroupPostURI($post); $item['uri'] = $this->extractGroupURI($post);
$item['title'] = $this->extractGroupPostTitle($post); $item['title'] = $this->extractGroupTitle($post);
$item['author'] = $this->extractGroupPostAuthor($post); $item['author'] = $this->extractGroupAuthor($post);
$item['content'] = $this->extractGroupPostContent($post); $item['content'] = $this->extractGroupContent($post);
$item['enclosures'] = $this->extractGroupPostEnclosures($post); $item['timestamp'] = $this->extractGroupTimestamp($post);
$item['enclosures'] = $this->extractGroupEnclosures($post);
$this->items[] = $item; $this->items[] = $item;
@@ -215,12 +179,22 @@ class FacebookBridge extends BridgeAbstract {
if(filter_var( if(filter_var(
$group, $group,
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) { FILTER_VALIDATE_URL,
FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED)) {
// User provided a URL // User provided a URL
$urlparts = parse_url($group); $urlparts = parse_url($group);
$this->validateHost($urlparts['host']); if($urlparts['host'] !== parse_url(self::URI)['host']
&& 'www.' . $urlparts['host'] !== parse_url(self::URI)['host']) {
returnClientError('The host you provided is invalid! Received "'
. $urlparts['host']
. '", expected "'
. parse_url(self::URI)['host']
. '"!');
}
return explode('/', $urlparts['path'])[2]; return explode('/', $urlparts['path'])[2];
@@ -232,47 +206,25 @@ class FacebookBridge extends BridgeAbstract {
} }
private function validateHost($provided_host) {
// Handle mobile links
if (strpos($provided_host, 'm.') === 0) {
$provided_host = substr($provided_host, strlen('m.'));
}
if (strpos($provided_host, 'touch.') === 0) {
$provided_host = substr($provided_host, strlen('touch.'));
}
$facebook_host = parse_url(self::URI)['host'];
if ($provided_host !== $facebook_host
&& 'www.' . $provided_host !== $facebook_host) {
returnClientError('The host you provided is invalid! Received "'
. $provided_host
. '", expected "'
. $facebook_host
. '"!');
}
}
/**
* @param $html simple_html_dom
* @return bool
*/
private function isPublicGroup($html) { private function isPublicGroup($html) {
// Facebook touch just presents a login page for non-public groups // Facebook redirects to the groups about page for non-public groups
$title = $html->find('title', 0); $about = $html->find('#pagelet_group_about', 0);
return $title->plaintext !== 'Log in to Facebook | Facebook';
return !($about);
} }
private function extractGroupName($html) { private function extractGroupName($html) {
$ogtitle = $html->find('._de1', 0) $ogtitle = $html->find('meta[property="og:title"]', 0)
or returnServerError('Unable to find group title!'); or returnServerError('Unable to find group title!');
return html_entity_decode($ogtitle->plaintext, ENT_QUOTES); return htmlspecialchars_decode($ogtitle->content, ENT_QUOTES);
} }
private function extractGroupPostURI($post) { private function extractGroupURI($post) {
$elements = $post->find('a') $elements = $post->find('a')
or returnServerError('Unable to find URI!'); or returnServerError('Unable to find URI!');
@@ -281,8 +233,7 @@ class FacebookBridge extends BridgeAbstract {
// Find the one that is a permalink // Find the one that is a permalink
if(strpos($anchor->href, 'permalink') !== false) { if(strpos($anchor->href, 'permalink') !== false) {
$arr = explode('?', $anchor->href, 2); return $anchor->href;
return $arr[0];
} }
} }
@@ -291,61 +242,57 @@ class FacebookBridge extends BridgeAbstract {
} }
private function extractGroupPostContent($post) { private function extractGroupContent($post) {
$content = $post->find('div._5rgt', 0) $content = $post->find('div.userContent', 0)
or returnServerError('Unable to find user content!'); or returnServerError('Unable to find user content!');
$context_text = $content->innertext; return $content->innertext . $content->next_sibling()->innertext;
if ($content->next_sibling() !== null) {
$context_text .= $content->next_sibling()->innertext;
}
return $context_text;
} }
private function extractGroupPostAuthor($post) { private function extractGroupTimestamp($post) {
$element = $post->find('h3 a', 0) $element = $post->find('abbr[data-utime]', 0)
or returnServerError('Unable to find timestamp!');
return $element->getAttribute('data-utime');
}
private function extractGroupAuthor($post) {
$element = $post->find('img', 0)
or returnServerError('Unable to find author information!'); or returnServerError('Unable to find author information!');
return $element->plaintext; return $element->{'aria-label'};
} }
private function extractGroupPostEnclosures($post) { private function extractGroupEnclosures($post) {
$elements = $post->find('span._6qdm'); $elements = $post->find('div.userContent', 0)->next_sibling()->find('img');
if ($post->find('div._5rgt', 0)->next_sibling() !== null) {
array_push($elements, ...$post->find('div._5rgt', 0)->next_sibling()->find('i.img'));
}
$enclosures = array(); $enclosures = array();
$background_img_regex = '/background-image: ?url\\((.+?)\\);/';
foreach($elements as $enclosure) { foreach($elements as $enclosure) {
if(preg_match($background_img_regex, $enclosure, $matches) > 0) { $enclosures[] = $enclosure->src;
$bg_img_value = trim(html_entity_decode($matches[1], ENT_QUOTES), "'\"");
$bg_img_url = urldecode(preg_replace('/\\\([0-9a-z]{2}) /', '%$1', $bg_img_value));
$enclosures[] = urldecode($bg_img_url);
}
} }
return empty($enclosures) ? null : $enclosures; return empty($enclosures) ? null : $enclosures;
} }
private function extractGroupPostTitle($post) { private function extractGroupTitle($post) {
$element = $post->find('h3', 0) $element = $post->find('h5', 0)
or returnServerError('Unable to find title!'); or returnServerError('Unable to find title!');
if(strpos($element->plaintext, 'shared') === false) { if(strpos($element->plaintext, 'shared') === false) {
$content = strip_tags($this->extractGroupPostContent($post)); $content = strip_tags($this->extractGroupContent($post));
return $this->extractGroupPostAuthor($post) return $this->extractGroupAuthor($post)
. ' posted: ' . ' posted: '
. substr( . substr(
$content, $content,
@@ -372,7 +319,13 @@ class FacebookBridge extends BridgeAbstract {
$urlparts = parse_url($user); $urlparts = parse_url($user);
$this->validateHost($urlparts['host']); if($urlparts['host'] !== parse_url(self::URI)['host']) {
returnClientError('The host you provided is invalid! Received "'
. $urlparts['host']
. '", expected "'
. parse_url(self::URI)['host']
. '"!');
}
if(!array_key_exists('path', $urlparts) if(!array_key_exists('path', $urlparts)
|| $urlparts['path'] === '/') { || $urlparts['path'] === '/') {
@@ -411,26 +364,6 @@ class FacebookBridge extends BridgeAbstract {
}, $content); }, $content);
} }
/**
* Remove Facebook's tracking code
*/
private function remove_tracking_codes($content){
return preg_replace_callback('/ href=\"([^"]+)\"/i', function($matches){
if(is_array($matches) && count($matches) > 1) {
$link = $matches[1];
if(strpos($link, 'facebook.com') !== false) {
if(strpos($link, '?') !== false) {
$link = substr($link, 0, strpos($link, '?'));
}
}
return ' href="' . $link . '"';
}
}, $content);
}
/** /**
* Convert textual representation of emoticons back to ASCII emoticons. * Convert textual representation of emoticons back to ASCII emoticons.
* i.e. "<i><u>smile emoticon</u></i>" => ":)" * i.e. "<i><u>smile emoticon</u></i>" => ":)"
@@ -493,7 +426,8 @@ class FacebookBridge extends BridgeAbstract {
// Show captcha filling form to the viewer, proxying the captcha image // Show captcha filling form to the viewer, proxying the captcha image
$img = base64_encode(getContents($captcha->find('img', 0)->src)); $img = base64_encode(getContents($captcha->find('img', 0)->src));
header('Content-Type: text/html', true, 500); http_response_code(500);
header('Content-Type: text/html');
$message = <<<EOD $message = <<<EOD
<form method="post" action="?{$_SERVER['QUERY_STRING']}"> <form method="post" action="?{$_SERVER['QUERY_STRING']}">
@@ -554,11 +488,7 @@ EOD;
// Retrieve page contents // Retrieve page contents
if(is_null($html)) { if(is_null($html)) {
if(getEnv('HTTP_ACCEPT_LANGUAGE')) {
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE')); $header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE'));
} else {
$header = array();
}
$html = getSimpleHTMLDOM($this->getURI(), $header) $html = getSimpleHTMLDOM($this->getURI(), $header)
or returnServerError('No results for this query.'); or returnServerError('No results for this query.');
@@ -573,7 +503,7 @@ EOD;
} }
// No captcha? We can carry on retrieving page contents :) // No captcha? We can carry on retrieving page contents :)
// First, we check whether the page is public or not // First, we check wether the page is public or not
$loginForm = $html->find('._585r', 0); $loginForm = $html->find('._585r', 0);
if($loginForm != null) { if($loginForm != null) {
@@ -589,7 +519,7 @@ EOD;
if(isset($element)) { if(isset($element)) {
$author = str_replace(' - Posts | Facebook', '', $html->find('title#pageTitle', 0)->innertext); $author = str_replace(' | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
$profilePic = $html->find('meta[property="og:image"]', 0)->content; $profilePic = $html->find('meta[property="og:image"]', 0)->content;
@@ -628,30 +558,10 @@ EOD;
$content = $post->find('.userContentWrapper', 0); $content = $post->find('.userContentWrapper', 0);
// This array specifies filters applied to all posts in order of appearance $content = preg_replace(
$content_filters = array( '/(?i)><div class=\"_59tj([^>]+)>(.+?)<\/div><\/div><a/i',
'._5mly', // Remove embedded videos (the preview image remains) '',
'._2ezg', // Remove "Views ..." $content);
'.hidden_elem', // Remove hidden elements (they are hidden anyway)
'.timestampContent', // Remove relative timestamp
'._6spk', // Remove redundant separator
);
foreach($content_filters as $filter) {
foreach($content->find($filter) as $subject) {
$subject->outertext = '';
}
}
// Change origin tag for embedded media from div to paragraph
foreach($content->find('._59tj') as $subject) {
$subject->outertext = '<p>' . $subject->innertext . '</p>';
}
// Change title tag for embedded media from anchor to paragraph
foreach($content->find('._3n1k a') as $anchor) {
$anchor->outertext = '<p>' . $anchor->innertext . '</p>';
}
$content = preg_replace( $content = preg_replace(
'/(?i)><div class=\"_3dp([^>]+)>(.+?)div\ class=\"[^u]+userContent\"/i', '/(?i)><div class=\"_3dp([^>]+)>(.+?)div\ class=\"[^u]+userContent\"/i',
@@ -701,8 +611,6 @@ EOD;
// Restore links in the content before adding to the item // Restore links in the content before adding to the item
$content = defaultLinkTo($content, self::URI); $content = defaultLinkTo($content, self::URI);
$content = $this->remove_tracking_codes($content);
// Retrieve date of the post // Retrieve date of the post
$date = $post->find('abbr')[0]; $date = $post->find('abbr')[0];
@@ -712,29 +620,28 @@ EOD;
$date = 0; $date = 0;
} }
// Build title from content // Build title from username and content
$title = strip_tags($post->find('.userContent', 0)->innertext); $title = $author;
if(strlen($title) > 24)
$title = substr($title, 0, strpos(wordwrap($title, 24), "\n")) . '...';
$title = $title . ' | ' . strip_tags($content);
if(strlen($title) > 64) if(strlen($title) > 64)
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...'; $title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
$uri = $post->find('abbr')[0]->parent()->getAttribute('href'); $uri = $post->find('abbr')[0]->parent()->getAttribute('href');
// Extract fbid and patch link if (false !== strpos($uri, '?')) {
if (strpos($uri, '?') !== false) {
$query = substr($uri, strpos($uri, '?') + 1);
parse_str($query, $query_params);
if (isset($query_params['story_fbid'])) {
$uri = self::URI . $query_params['story_fbid'];
} else {
$uri = substr($uri, 0, strpos($uri, '?')); $uri = substr($uri, 0, strpos($uri, '?'));
} }
}
//Build and add final item //Build and add final item
$item['uri'] = htmlspecialchars_decode($uri, ENT_QUOTES); $item['uri'] = htmlspecialchars_decode($uri);
$item['content'] = htmlspecialchars_decode($content, ENT_QUOTES); $item['content'] = htmlspecialchars_decode($content);
$item['title'] = htmlspecialchars_decode($title, ENT_QUOTES); $item['title'] = $title;
$item['author'] = htmlspecialchars_decode($author, ENT_QUOTES); $item['author'] = $author;
$item['timestamp'] = $date; $item['timestamp'] = $date;
if(strpos($item['content'], '<img') === false) { if(strpos($item['content'], '<img') === false) {

View File

@@ -3,7 +3,7 @@ class FeedExpanderExampleBridge extends FeedExpander {
const MAINTAINER = 'logmanoriginal'; const MAINTAINER = 'logmanoriginal';
const NAME = 'FeedExpander Example'; const NAME = 'FeedExpander Example';
const URI = 'http://github.com/RSS-Bridge/rss-bridge/'; const URI = '#';
const DESCRIPTION = 'Example bridge to test FeedExpander'; const DESCRIPTION = 'Example bridge to test FeedExpander';
const PARAMETERS = array( const PARAMETERS = array(
@@ -11,6 +11,7 @@ class FeedExpanderExampleBridge extends FeedExpander {
'version' => array( 'version' => array(
'name' => 'Version', 'name' => 'Version',
'type' => 'list', 'type' => 'list',
'required' => true,
'title' => 'Select your feed format/version', 'title' => 'Select your feed format/version',
'defaultValue' => 'RSS 2.0', 'defaultValue' => 'RSS 2.0',
'values' => array( 'values' => array(

View File

@@ -1,185 +0,0 @@
<?php
class FicbookBridge extends BridgeAbstract {
const NAME = 'Ficbook Bridge';
const URI = 'https://ficbook.net/';
const DESCRIPTION = 'No description provided';
const MAINTAINER = 'logmanoriginal';
const PARAMETERS = array(
'Site News' => array(),
'Fiction Updates' => array(
'fiction_id' => array(
'name' => 'Fanfiction ID',
'type' => 'text',
'pattern' => '[0-9]+',
'required' => true,
'title' => 'Insert fanfiction ID',
'exampleValue' => '5783919',
),
'include_contents' => array(
'name' => 'Include contents',
'type' => 'checkbox',
'title' => 'Activate to include contents in the feed',
),
),
'Fiction Comments' => array(
'fiction_id' => array(
'name' => 'Fanfiction ID',
'type' => 'text',
'pattern' => '[0-9]+',
'required' => true,
'title' => 'Insert fanfiction ID',
'exampleValue' => '5783919',
),
),
);
protected $titleName;
public function getURI() {
switch($this->queriedContext) {
case 'Site News': {
// For some reason this is not HTTPS
return 'http://ficbook.net/sitenews';
}
case 'Fiction Updates': {
return self::URI
. 'readfic/'
. urlencode($this->getInput('fiction_id'));
}
case 'Fiction Comments': {
return self::URI
. 'readfic/'
. urlencode($this->getInput('fiction_id'))
. '/comments#content';
}
default: return parent::getURI();
}
}
public function getName() {
switch($this->queriedContext) {
case 'Site News': {
return $this->queriedContext . ' | ' . self::NAME;
}
case 'Fiction Updates': {
return $this->titleName . ' | ' . self::NAME;
}
case 'Fiction Comments': {
return $this->titleName . ' | Comments | ' . self::NAME;
}
default: return self::NAME;
}
}
public function collectData() {
$header = array('Accept-Language: en-US');
$html = getSimpleHTMLDOM($this->getURI(), $header)
or returnServerError('Could not request ' . $this->getURI());
$html = defaultLinkTo($html, self::URI);
if ($this->queriedContext == 'Fiction Updates' or $this->queriedContext == 'Fiction Comments') {
$this->titleName = $html->find('.fanfic-main-info > h1', 0)->innertext;
}
switch($this->queriedContext) {
case 'Site News': return $this->collectSiteNews($html);
case 'Fiction Updates': return $this->collectUpdatesData($html);
case 'Fiction Comments': return $this->collectCommentsData($html);
}
}
private function collectSiteNews($html) {
foreach($html->find('.news_view') as $news) {
$this->items[] = array(
'title' => $news->find('h1.title', 0)->plaintext,
'timestamp' => strtotime($this->fixDate($news->find('span[title]', 0)->title)),
'content' => $news->find('.news_text', 0),
);
}
}
private function collectCommentsData($html) {
foreach($html->find('article.comment-container') as $article) {
$this->items[] = array(
'uri' => $article->find('.comment_link_to_fic > a', 0)->href,
'title' => $article->find('.comment_author', 0)->plaintext,
'author' => $article->find('.comment_author', 0)->plaintext,
'timestamp' => strtotime($this->fixDate($article->find('time[datetime]', 0)->datetime)),
'content' => $article->find('.comment_message', 0),
'enclosures' => array($article->find('img', 0)->src),
);
}
}
private function collectUpdatesData($html) {
foreach($html->find('ul.list-of-fanfic-parts > li') as $chapter) {
$item = array(
'uri' => $chapter->find('a', 0)->href,
'title' => $chapter->find('a', 0)->plaintext,
'timestamp' => strtotime($this->fixDate($chapter->find('span[title]', 0)->title)),
);
if($this->getInput('include_contents')) {
$content = getSimpleHTMLDOMCached($item['uri']);
$item['content'] = $content->find('#content', 0);
}
$this->items[] = $item;
// Sort by time, descending
usort($this->items, function($a, $b){ return $b['timestamp'] - $a['timestamp']; });
}
}
private function fixDate($date) {
// FIXME: This list was generated using Google tranlator. Someone who
// actually knows russian should check this list! Please keep in mind
// that month names must match exactly the names returned by Ficbook.
$ru_month = array(
'января',
'февраля',
'марта',
'апреля',
'мая',
'июня',
'июля',
'августа',
'сентября',
'октября',
'ноября',
'декабря',
);
$en_month = array(
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
);
$fixed_date = str_replace($ru_month, $en_month, $date);
if($fixed_date === $date) {
Debug::log('Unable to fix date: ' . $date);
return null;
}
return $fixed_date;
}
}

View File

@@ -94,7 +94,7 @@ class FilterBridge extends FeedExpander {
} }
try{ try{
$this->collectExpandableDatas($this->getURI()); $this->collectExpandableDatas($this->getURI());
} catch (Exception $e) { } catch (HttpException $e) {
$this->collectExpandableDatas($this->getURI()); $this->collectExpandableDatas($this->getURI());
} }
} }

Some files were not shown because too many files have changed in this diff Show More