mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-26 09:34:46 +02:00
Compare commits
78 Commits
2018-10-15
...
2018-11-10
Author | SHA1 | Date | |
---|---|---|---|
|
a08811f147 | ||
|
a935e310ff | ||
|
7e3787a185 | ||
|
039c032798 | ||
|
cb91d9cce8 | ||
|
bf91f106b4 | ||
|
0b2ede35cd | ||
|
5842bdfc83 | ||
|
68ee24d6bd | ||
|
104ae2298e | ||
|
7026684e34 | ||
|
0b792d77eb | ||
|
110b865a54 | ||
|
19a7f10160 | ||
|
42e25e7fc0 | ||
|
4b7fea5ebc | ||
|
95bd206e9d | ||
|
9910310652 | ||
|
12f0e5a360 | ||
|
81ba96ff94 | ||
|
984f0b24d0 | ||
|
2126db84ac | ||
|
4bf45df18e | ||
|
a88b148d20 | ||
|
f564925ba0 | ||
|
22e8f8b4aa | ||
|
bfae04d1fe | ||
|
723bd1150a | ||
|
53d2fbe3a5 | ||
|
3babd02658 | ||
|
3031fa406d | ||
|
85c34a0960 | ||
|
5deb86acff | ||
|
946e66e9df | ||
|
1a00dfa412 | ||
|
0f8443e1d3 | ||
|
7d474e5361 | ||
|
8c97953211 | ||
|
d987ceec73 | ||
|
392e3ff6c7 | ||
|
e295dc5a79 | ||
|
b9f6bc8197 | ||
|
9c1c0f2974 | ||
|
65da157fff | ||
|
5fe943562a | ||
|
c58331f74d | ||
|
145a46ae1d | ||
|
1a7a7bad98 | ||
|
27d6a22675 | ||
|
b55ec51e0e | ||
|
07b4c72d5d | ||
|
2e6cbd1ce7 | ||
|
2ac2f3dc66 | ||
|
e2dfea2b77 | ||
|
c4896c7791 | ||
|
7621784598 | ||
|
1cfe939927 | ||
|
c56f7abc2a | ||
|
e3030cbbfd | ||
|
953c6e1022 | ||
|
dbd44f64dd | ||
|
89ca42da54 | ||
|
b4b5340b7e | ||
|
a508dddb36 | ||
|
cb488d9d8c | ||
|
9820ad5c0f | ||
|
ea2d54523d | ||
|
87d218296e | ||
|
afd5ef0f1d | ||
|
30bc5179c2 | ||
|
7596be65f2 | ||
|
16f0ee7104 | ||
|
e0323f06cd | ||
|
717b0bdd9c | ||
|
62d737efe2 | ||
|
6fce03daa7 | ||
|
7561c0685d | ||
|
f48eac854f |
@@ -4,5 +4,4 @@ DEBUG
|
||||
Dockerfile
|
||||
whitelist.txt
|
||||
phpcs.xml
|
||||
CHANGELOG.md
|
||||
CONTRIBUTING.md
|
47
.github/CONTRIBUTING.md
vendored
Normal file
47
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
### Pull request policy
|
||||
|
||||
* [Fix one issue per pull request](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#fix-one-issue-per-pull-request)
|
||||
* [Respect the coding style policy](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#respect-the-coding-style-policy)
|
||||
* [Properly name your commits](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#properly-name-your-commits)
|
||||
* When fixing a bridge (located in the `bridges` directory), write `[BridgeName] Feature` <br>(i.e. `[YoutubeBridge] Fix typo in video titles`).
|
||||
* When fixing other files, use `[FileName] Feature` <br>(i.e. `[index.php] Add multilingual support`).
|
||||
* When fixing a general problem that applies to multiple files, write `category: feature` <br>(i.e. `bridges: Fix various typos`).
|
||||
|
||||
Note that all pull-requests must pass all tests before they can be merged.
|
||||
|
||||
### Coding style
|
||||
|
||||
* [Whitespace](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace)
|
||||
* [Add a new line at the end of a file](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#add-a-new-line-at-the-end-of-a-file)
|
||||
* [Do not add a whitespace before a semicolon](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#add-a-new-line-at-the-end-of-a-file)
|
||||
* [Do not add whitespace at start or end of a file or end of a line](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#do-not-add-whitespace-at-start-or-end-of-a-file-or-end-of-a-line)
|
||||
* [Indentation](https://github.com/RSS-Bridge/rss-bridge/wiki/Indentation)
|
||||
* [Use tabs for indentation](https://github.com/RSS-Bridge/rss-bridge/wiki/Indentation#use-tabs-for-indentation)
|
||||
* [Maximum line length](https://github.com/RSS-Bridge/rss-bridge/wiki/Maximum-line-length)
|
||||
* [The maximum line length should not exceed 80 characters](https://github.com/RSS-Bridge/rss-bridge/wiki/Maximum-line-length#the-maximum-line-length-should-not-exceed-80-characters)
|
||||
* [Strings](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings)
|
||||
* [Whenever possible use single quoted strings](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#whenever-possible-use-single-quote-strings)
|
||||
* [Add spaces around the concatenation operator](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#add-spaces-around-the-concatenation-operator)
|
||||
* [Use a single string instead of concatenating](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#use-a-single-string-instead-of-concatenating)
|
||||
* [Constants](https://github.com/RSS-Bridge/rss-bridge/wiki/Constants)
|
||||
* [Use UPPERCASE for constants](https://github.com/RSS-Bridge/rss-bridge/wiki/Constants#use-uppercase-for-constants)
|
||||
* [Keywords](https://github.com/RSS-Bridge/rss-bridge/wiki/Keywords)
|
||||
* [Use lowercase for `true`, `false` and `null`](https://github.com/RSS-Bridge/rss-bridge/wiki/Keywords#use-lowercase-for-true-false-and-null)
|
||||
* [Operators](https://github.com/RSS-Bridge/rss-bridge/wiki/Operators)
|
||||
* [Operators must have a space around them](https://github.com/RSS-Bridge/rss-bridge/wiki/Operators#operators-must-have-a-space-around-them)
|
||||
* [Functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions)
|
||||
* [Parameters with default values must appear last in functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#parameters-with-default-values-must-appear-last-in-functions)
|
||||
* [Calling functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#calling-functions)
|
||||
* [Do not add spaces after opening or before closing bracket](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#do-not-add-spaces-after-opening-or-before-closing-bracket)
|
||||
* [Structures](https://github.com/RSS-Bridge/rss-bridge/wiki/Structures)
|
||||
* [Structures must always be formatted as multi-line blocks](https://github.com/RSS-Bridge/rss-bridge/wiki/Structures#structures-must-always-be-formatted-as-multi-line-blocks)
|
||||
* [If-Statement](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement)
|
||||
* [Use `elseif` instead of `else if`](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#use-elseif-instead-of-else-if)
|
||||
* [Do not write empty statements](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#do-not-write-empty-statements)
|
||||
* [Do not write unconditional if-statements](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#do-not-write-unconditional-if-statements)
|
||||
* [Classes](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes)
|
||||
* [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 override methods to call their parent](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-override-methods-to-call-their-parent)
|
||||
* [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)
|
61
.github/ISSUE_TEMPLATE/bridge-request-template.md
vendored
Normal file
61
.github/ISSUE_TEMPLATE/bridge-request-template.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: Bridge request template
|
||||
about: Use this template for requesting a new bridge
|
||||
|
||||
---
|
||||
|
||||
# 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.
|
||||
-->
|
31
.travis.yml
31
.travis.yml
@@ -3,26 +3,35 @@ 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
|
||||
- composer global require dealerdirect/phpcodesniffer-composer-installer;
|
||||
- composer global require phpcompatibility/php-compatibility;
|
||||
# Use PHPUnit 6 for unit tests (stable), requires PHP 7
|
||||
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
|
||||
composer global require phpunit/phpunit ^6;
|
||||
fi
|
||||
# Use latest PHPUnit on nightly to detect breaking changes
|
||||
- if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then
|
||||
composer global require phpunit/phpunit;
|
||||
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;
|
||||
# Run PHP_CodeSniffer on all versions
|
||||
- ~/.composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
||||
# Check PHP compatibility for the lowest supported version
|
||||
- if [[ $TRAVIS_PHP_VERSION == "5.6" ]]; then
|
||||
~/.composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --warning-severity=0 --extensions=php -p;
|
||||
fi
|
||||
# Run unit tests (stable)
|
||||
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
|
||||
phpunit --configuration=phpunit.xml --include-path=lib/;
|
||||
fi
|
||||
# Run unit tests (latest/nightly)
|
||||
# Check PHP compatibility for all versions, starting at the lowest supported version in order to detect breaking changes
|
||||
- if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then
|
||||
phpunit --configuration=phpunit.xml --include-path=lib/;
|
||||
~/.composer/vendor/bin/phpcs . --standard=PHPCompatibility --warning-severity=0 --extensions=php -p --runtime-set testVersion 5.6-;
|
||||
fi
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
@@ -30,9 +39,7 @@ matrix:
|
||||
include:
|
||||
- php: 5.6
|
||||
- php: 7.0
|
||||
- php: hhvm
|
||||
- php: nightly
|
||||
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
- php: nightly
|
||||
|
263
CHANGELOG.md
263
CHANGELOG.md
@@ -1,263 +0,0 @@
|
||||
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 & 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.
|
@@ -1,47 +0,0 @@
|
||||
### Pull request policy
|
||||
Fix one issue per pull request.
|
||||
Squash commits before opening a pull request.
|
||||
Respect the coding style policy.
|
||||
Name your PR like the following :
|
||||
|
||||
* When correcting a single bridge, use `[BridgeName] Feature`.
|
||||
* When fixing a problem in a specific file, use `[FileName] Feature`.
|
||||
* When fixing a general problem, use `category : feature`.
|
||||
|
||||
Note that all pull-requests should pass the unit tests before they can be merged.
|
||||
|
||||
### Coding style
|
||||
|
||||
Use `camelCase` for variables and methods.
|
||||
Use `UPPERCASE` for constants.
|
||||
Use `PascalCase` for class names. When creating a bridge, your class and PHP file should be named `MyImplementationBridge`.
|
||||
Use tabs for indentation.
|
||||
Add an empty line at the end of your file.
|
||||
|
||||
Use `''` to encapsulate strings, including in arrays.
|
||||
Prefer lines shorter than 80 chars, no line longer than 120 chars.
|
||||
PHP constants should be in lower case (`true, false, null`...)
|
||||
|
||||
|
||||
* Add spaces between the logical operator and your expressions (not needed for the `!` operator).
|
||||
* Use `||` and `&&` instead of `or` and `and`.
|
||||
* Add space between your condition and the opening bracket/closing bracket.
|
||||
* Don't put a space between `if` and your bracket.
|
||||
* Use `elseif` instead of `else if`.
|
||||
* Add new lines in your conditions if they are containing more than one line.
|
||||
* Example :
|
||||
|
||||
```PHP
|
||||
if($a == true && $b) {
|
||||
print($a);
|
||||
} else if(!$b) {
|
||||
|
||||
$a = !$a;
|
||||
$b = $b >> $a;
|
||||
print($b);
|
||||
|
||||
} else {
|
||||
print($b);
|
||||
}
|
||||
```
|
||||
|
@@ -4,6 +4,8 @@ rss-bridge
|
||||
|
||||
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).
|
||||
|
||||
Supported sites/pages (examples)
|
||||
===
|
||||
|
||||
|
@@ -8,7 +8,7 @@ class ABCTabsBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$html = getSimpleHTMLDOM(static::URI.'tablatures/nouveautes.html')
|
||||
$html = getSimpleHTMLDOM(static::URI . 'tablatures/nouveautes.html')
|
||||
or returnClientError('No results for this query.');
|
||||
|
||||
$table = $html->find('table#myTable', 0)->children(1);
|
||||
|
@@ -45,7 +45,7 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('category'))) {
|
||||
return self::NAME . ' : '
|
||||
.array_search(
|
||||
. array_search(
|
||||
$this->getInput('category'),
|
||||
self::PARAMETERS[$this->queriedContext]['category']['values']
|
||||
);
|
||||
|
@@ -52,7 +52,7 @@ class AmazonBridge extends BridgeAbstract {
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('tld')) && !is_null($this->getInput('q'))) {
|
||||
return 'Amazon.'.$this->getInput('tld').': '.$this->getInput('q');
|
||||
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('q');
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
@@ -60,8 +60,8 @@ class AmazonBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$uri = 'https://www.amazon.'.$this->getInput('tld').'/';
|
||||
$uri .= 's/?field-keywords='.urlencode($this->getInput('q')).'&sort='.$this->getInput('sort');
|
||||
$uri = 'https://www.amazon.' . $this->getInput('tld') . '/';
|
||||
$uri .= 's/?field-keywords=' . urlencode($this->getInput('q')) . '&sort=' . $this->getInput('sort');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not request Amazon.');
|
||||
@@ -86,7 +86,7 @@ class AmazonBridge extends BridgeAbstract {
|
||||
$price = $element->find('span.s-price', 0);
|
||||
$price = ($price) ? $price->innertext : '';
|
||||
|
||||
$item['content'] = '<img src="'.$image->getAttribute('src').'" /><br />'.$price;
|
||||
$item['content'] = '<img src="' . $image->getAttribute('src') . '" /><br />' . $price;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -140,7 +140,7 @@ class AnidexBridge extends BridgeAbstract {
|
||||
if (strpos($link->href, '/torrent/') === 0 && !in_array($link->href, $results))
|
||||
$results[] = $link->href;
|
||||
if (empty($results) && empty($this->getInput('q')))
|
||||
returnServerError('No results from Anidex: '.$search_url);
|
||||
returnServerError('No results from Anidex: ' . $search_url);
|
||||
|
||||
//Process each item individually
|
||||
foreach ($results as $element) {
|
||||
@@ -156,7 +156,7 @@ class AnidexBridge extends BridgeAbstract {
|
||||
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
|
||||
|
||||
//Retrieve data for this torrent ID
|
||||
$item_uri = self::URI . 'torrent/'.$torrent_id;
|
||||
$item_uri = self::URI . 'torrent/' . $torrent_id;
|
||||
|
||||
//Retrieve full description from torrent page
|
||||
if ($item_html = getSimpleHTMLDOMCached($item_uri)) {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class AskfmBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'az5he6ch';
|
||||
const MAINTAINER = 'az5he6ch, logmanoriginal';
|
||||
const NAME = 'Ask.fm Answers';
|
||||
const URI = 'https://ask.fm/';
|
||||
const CACHE_TIMEOUT = 300; //5 min
|
||||
@@ -19,39 +19,39 @@ class AskfmBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Requested username can\'t be found.');
|
||||
|
||||
foreach($html->find('div.streamItem-answer') as $element) {
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
foreach($html->find('article.streamItem-answer') as $element) {
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $element->find('a.streamItemsAge', 0)->href;
|
||||
$question = trim($element->find('h1.streamItemContent-question', 0)->innertext);
|
||||
$item['uri'] = $element->find('a.streamItem_meta', 0)->href;
|
||||
$question = trim($element->find('header.streamItem_header', 0)->innertext);
|
||||
|
||||
$item['title'] = trim(
|
||||
htmlspecialchars_decode($element->find('h1.streamItemContent-question', 0)->plaintext,
|
||||
htmlspecialchars_decode($element->find('header.streamItem_header', 0)->plaintext,
|
||||
ENT_QUOTES
|
||||
)
|
||||
);
|
||||
|
||||
$answer = trim($element->find('p.streamItemContent-answer', 0)->innertext);
|
||||
$item['timestamp'] = strtotime($element->find('time', 0)->datetime);
|
||||
|
||||
// Doesn't work, DOM parser doesn't seem to like data-hint, dunno why
|
||||
#$item['update'] = $element->find('a.streamitemsage',0)->data-hint;
|
||||
$answer = trim($element->find('div.streamItem_content', 0)->innertext);
|
||||
|
||||
// This probably should be cleaned up, especially for YouTube embeds
|
||||
$visual = $element->find('div.streamItemContent-visual', 0)->innertext;
|
||||
//Fix tracking links, also doesn't work
|
||||
if($visual = $element->find('div.streamItem_visual', 0)) {
|
||||
$visual = $visual->innertext;
|
||||
}
|
||||
|
||||
// Fix tracking links, also doesn't work
|
||||
foreach($element->find('a') as $link) {
|
||||
if(strpos($link->href, 'l.ask.fm') !== false) {
|
||||
|
||||
// Too slow
|
||||
#$link->href = str_replace('#_=_', '', get_headers($link->href, 1)['Location']);
|
||||
|
||||
$link->href = $link->plaintext;
|
||||
}
|
||||
}
|
||||
|
||||
$content = '<p>' . $question . '</p><p>' . $answer . '</p><p>' . $visual . '</p>';
|
||||
// Fix relative links without breaking // scheme used by YouTube stuff
|
||||
$content = preg_replace('#href="\/(?!\/)#', 'href="' . self::URI, $content);
|
||||
$item['content'] = $content;
|
||||
$item['content'] = '<p>' . $question
|
||||
. '</p><p>' . $answer
|
||||
. '</p><p>' . $visual . '</p>';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class AskfmBridge extends BridgeAbstract {
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return self::URI . urlencode($this->getInput('u')) . '/answers/more?page=0';
|
||||
return self::URI . urlencode($this->getInput('u'));
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
|
@@ -19,6 +19,10 @@ class AutoJMBridge extends BridgeAbstract {
|
||||
);
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/images/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
|
||||
or returnServerError('Could not request AutoJM.');
|
||||
@@ -43,7 +47,7 @@ class AutoJMBridge extends BridgeAbstract {
|
||||
$item = array();
|
||||
$item['uri'] = $url;
|
||||
$item['title'] = $serie;
|
||||
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />'. $serie . '</p>';
|
||||
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />' . $serie . '</p>';
|
||||
$item['content'] .= '<ul><li>Disponibilité : ' . $dispo . '</li>';
|
||||
$item['content'] .= '<li>Carburant : ' . $carburant . '</li>';
|
||||
$item['content'] .= '<li>Transmission : ' . $transmission . '</li>';
|
||||
@@ -59,4 +63,3 @@ class AutoJMBridge extends BridgeAbstract {
|
||||
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
265
bridges/BAEBridge.php
Normal file
265
bridges/BAEBridge.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
class BAEBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'couraudt';
|
||||
const NAME = 'Bourse Aux Equipiers Bridge';
|
||||
const URI = 'https://www.bourse-aux-equipiers.com';
|
||||
const DESCRIPTION = 'Returns the newest sailing offers.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'keyword' => array(
|
||||
'name' => 'Filtrer par mots clés',
|
||||
'title' => 'Entrez le mot clé à filtrer ici'
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Type de recherche',
|
||||
'title' => 'Afficher seuleument un certain type d\'annonce',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Toutes les annonces' => false,
|
||||
'Les embarquements' => 'boat',
|
||||
'Les skippers' => 'skipper',
|
||||
'Les équipiers' => 'crew'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getURI();
|
||||
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
|
||||
|
||||
$annonces = $html->find('main article');
|
||||
foreach ($annonces as $annonce) {
|
||||
$detail = $annonce->find('footer a', 0);
|
||||
|
||||
$htmlDetail = getSimpleHTMLDOMCached(parent::getURI() . $detail->href);
|
||||
if (!$htmlDetail)
|
||||
continue;
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $annonce->find('header h2', 0)->plaintext;
|
||||
$item['uri'] = parent::getURI() . $detail->href;
|
||||
|
||||
$content = $htmlDetail->find('article p', 0)->innertext;
|
||||
if (!empty($this->getInput('keyword'))) {
|
||||
$keyword = $this->remove_accents(strtolower($this->getInput('keyword')));
|
||||
$cleanTitle = $this->remove_accents(strtolower($item['title']));
|
||||
if (strpos($cleanTitle, $keyword) === false) {
|
||||
$cleanContent = $this->remove_accents(strtolower($content));
|
||||
if (strpos($cleanContent, $keyword) === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$content .= '<hr>';
|
||||
$content .= $htmlDetail->find('section', 0)->innertext;
|
||||
$content = str_replace('src="/', 'src="' . parent::getURI() . '/', $content);
|
||||
$content = str_replace('href="/', 'href="' . parent::getURI() . '/', $content);
|
||||
$item['content'] = $content;
|
||||
$image = $htmlDetail->find('#zoom', 0);
|
||||
if ($image) {
|
||||
$item['enclosures'] = array(parent::getURI() . $image->getAttribute('src'));
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = parent::getURI();
|
||||
if (!empty($this->getInput('type'))) {
|
||||
if ($this->getInput('type') == 'boat') {
|
||||
$uri .= '/embarquements.html';
|
||||
} elseif ($this->getInput('type') == 'skipper') {
|
||||
$uri .= '/skippers.html';
|
||||
} else {
|
||||
$uri .= '/equipiers.html';
|
||||
}
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
private function remove_accents($string) {
|
||||
$chars = array(
|
||||
// Decompositions for Latin-1 Supplement
|
||||
'ª' => 'a', 'º' => 'o',
|
||||
'À' => 'A', 'Á' => 'A',
|
||||
'Â' => 'A', 'Ã' => 'A',
|
||||
'Ä' => 'A', 'Å' => 'A',
|
||||
'Æ' => 'AE', 'Ç' => 'C',
|
||||
'È' => 'E', 'É' => 'E',
|
||||
'Ê' => 'E', 'Ë' => 'E',
|
||||
'Ì' => 'I', 'Í' => 'I',
|
||||
'Î' => 'I', 'Ï' => 'I',
|
||||
'Ð' => 'D', 'Ñ' => 'N',
|
||||
'Ò' => 'O', 'Ó' => 'O',
|
||||
'Ô' => 'O', 'Õ' => 'O',
|
||||
'Ö' => 'O', 'Ù' => 'U',
|
||||
'Ú' => 'U', 'Û' => 'U',
|
||||
'Ü' => 'U', 'Ý' => 'Y',
|
||||
'Þ' => 'TH', 'ß' => 's',
|
||||
'à' => 'a', 'á' => 'a',
|
||||
'â' => 'a', 'ã' => 'a',
|
||||
'ä' => 'a', 'å' => 'a',
|
||||
'æ' => 'ae', 'ç' => 'c',
|
||||
'è' => 'e', 'é' => 'e',
|
||||
'ê' => 'e', 'ë' => 'e',
|
||||
'ì' => 'i', 'í' => 'i',
|
||||
'î' => 'i', 'ï' => 'i',
|
||||
'ð' => 'd', 'ñ' => 'n',
|
||||
'ò' => 'o', 'ó' => 'o',
|
||||
'ô' => 'o', 'õ' => 'o',
|
||||
'ö' => 'o', 'ø' => 'o',
|
||||
'ù' => 'u', 'ú' => 'u',
|
||||
'û' => 'u', 'ü' => 'u',
|
||||
'ý' => 'y', 'þ' => 'th',
|
||||
'ÿ' => 'y', 'Ø' => 'O',
|
||||
// Decompositions for Latin Extended-A
|
||||
'Ā' => 'A', 'ā' => 'a',
|
||||
'Ă' => 'A', 'ă' => 'a',
|
||||
'Ą' => 'A', 'ą' => 'a',
|
||||
'Ć' => 'C', 'ć' => 'c',
|
||||
'Ĉ' => 'C', 'ĉ' => 'c',
|
||||
'Ċ' => 'C', 'ċ' => 'c',
|
||||
'Č' => 'C', 'č' => 'c',
|
||||
'Ď' => 'D', 'ď' => 'd',
|
||||
'Đ' => 'D', 'đ' => 'd',
|
||||
'Ē' => 'E', 'ē' => 'e',
|
||||
'Ĕ' => 'E', 'ĕ' => 'e',
|
||||
'Ė' => 'E', 'ė' => 'e',
|
||||
'Ę' => 'E', 'ę' => 'e',
|
||||
'Ě' => 'E', 'ě' => 'e',
|
||||
'Ĝ' => 'G', 'ĝ' => 'g',
|
||||
'Ğ' => 'G', 'ğ' => 'g',
|
||||
'Ġ' => 'G', 'ġ' => 'g',
|
||||
'Ģ' => 'G', 'ģ' => 'g',
|
||||
'Ĥ' => 'H', 'ĥ' => 'h',
|
||||
'Ħ' => 'H', 'ħ' => 'h',
|
||||
'Ĩ' => 'I', 'ĩ' => 'i',
|
||||
'Ī' => 'I', 'ī' => 'i',
|
||||
'Ĭ' => 'I', 'ĭ' => 'i',
|
||||
'Į' => 'I', 'į' => 'i',
|
||||
'İ' => 'I', 'ı' => 'i',
|
||||
'IJ' => 'IJ', 'ij' => 'ij',
|
||||
'Ĵ' => 'J', 'ĵ' => 'j',
|
||||
'Ķ' => 'K', 'ķ' => 'k',
|
||||
'ĸ' => 'k', 'Ĺ' => 'L',
|
||||
'ĺ' => 'l', 'Ļ' => 'L',
|
||||
'ļ' => 'l', 'Ľ' => 'L',
|
||||
'ľ' => 'l', 'Ŀ' => 'L',
|
||||
'ŀ' => 'l', 'Ł' => 'L',
|
||||
'ł' => 'l', 'Ń' => 'N',
|
||||
'ń' => 'n', 'Ņ' => 'N',
|
||||
'ņ' => 'n', 'Ň' => 'N',
|
||||
'ň' => 'n', 'ʼn' => 'n',
|
||||
'Ŋ' => 'N', 'ŋ' => 'n',
|
||||
'Ō' => 'O', 'ō' => 'o',
|
||||
'Ŏ' => 'O', 'ŏ' => 'o',
|
||||
'Ő' => 'O', 'ő' => 'o',
|
||||
'Œ' => 'OE', 'œ' => 'oe',
|
||||
'Ŕ' => 'R', 'ŕ' => 'r',
|
||||
'Ŗ' => 'R', 'ŗ' => 'r',
|
||||
'Ř' => 'R', 'ř' => 'r',
|
||||
'Ś' => 'S', 'ś' => 's',
|
||||
'Ŝ' => 'S', 'ŝ' => 's',
|
||||
'Ş' => 'S', 'ş' => 's',
|
||||
'Š' => 'S', 'š' => 's',
|
||||
'Ţ' => 'T', 'ţ' => 't',
|
||||
'Ť' => 'T', 'ť' => 't',
|
||||
'Ŧ' => 'T', 'ŧ' => 't',
|
||||
'Ũ' => 'U', 'ũ' => 'u',
|
||||
'Ū' => 'U', 'ū' => 'u',
|
||||
'Ŭ' => 'U', 'ŭ' => 'u',
|
||||
'Ů' => 'U', 'ů' => 'u',
|
||||
'Ű' => 'U', 'ű' => 'u',
|
||||
'Ų' => 'U', 'ų' => 'u',
|
||||
'Ŵ' => 'W', 'ŵ' => 'w',
|
||||
'Ŷ' => 'Y', 'ŷ' => 'y',
|
||||
'Ÿ' => 'Y', 'Ź' => 'Z',
|
||||
'ź' => 'z', 'Ż' => 'Z',
|
||||
'ż' => 'z', 'Ž' => 'Z',
|
||||
'ž' => 'z', 'ſ' => 's',
|
||||
// Decompositions for Latin Extended-B
|
||||
'Ș' => 'S', 'ș' => 's',
|
||||
'Ț' => 'T', 'ț' => 't',
|
||||
// Euro Sign
|
||||
'€' => 'E',
|
||||
// GBP (Pound) Sign
|
||||
'£' => '',
|
||||
// Vowels with diacritic (Vietnamese)
|
||||
// unmarked
|
||||
'Ơ' => 'O', 'ơ' => 'o',
|
||||
'Ư' => 'U', 'ư' => 'u',
|
||||
// grave accent
|
||||
'Ầ' => 'A', 'ầ' => 'a',
|
||||
'Ằ' => 'A', 'ằ' => 'a',
|
||||
'Ề' => 'E', 'ề' => 'e',
|
||||
'Ồ' => 'O', 'ồ' => 'o',
|
||||
'Ờ' => 'O', 'ờ' => 'o',
|
||||
'Ừ' => 'U', 'ừ' => 'u',
|
||||
'Ỳ' => 'Y', 'ỳ' => 'y',
|
||||
// hook
|
||||
'Ả' => 'A', 'ả' => 'a',
|
||||
'Ẩ' => 'A', 'ẩ' => 'a',
|
||||
'Ẳ' => 'A', 'ẳ' => 'a',
|
||||
'Ẻ' => 'E', 'ẻ' => 'e',
|
||||
'Ể' => 'E', 'ể' => 'e',
|
||||
'Ỉ' => 'I', 'ỉ' => 'i',
|
||||
'Ỏ' => 'O', 'ỏ' => 'o',
|
||||
'Ổ' => 'O', 'ổ' => 'o',
|
||||
'Ở' => 'O', 'ở' => 'o',
|
||||
'Ủ' => 'U', 'ủ' => 'u',
|
||||
'Ử' => 'U', 'ử' => 'u',
|
||||
'Ỷ' => 'Y', 'ỷ' => 'y',
|
||||
// tilde
|
||||
'Ẫ' => 'A', 'ẫ' => 'a',
|
||||
'Ẵ' => 'A', 'ẵ' => 'a',
|
||||
'Ẽ' => 'E', 'ẽ' => 'e',
|
||||
'Ễ' => 'E', 'ễ' => 'e',
|
||||
'Ỗ' => 'O', 'ỗ' => 'o',
|
||||
'Ỡ' => 'O', 'ỡ' => 'o',
|
||||
'Ữ' => 'U', 'ữ' => 'u',
|
||||
'Ỹ' => 'Y', 'ỹ' => 'y',
|
||||
// acute accent
|
||||
'Ấ' => 'A', 'ấ' => 'a',
|
||||
'Ắ' => 'A', 'ắ' => 'a',
|
||||
'Ế' => 'E', 'ế' => 'e',
|
||||
'Ố' => 'O', 'ố' => 'o',
|
||||
'Ớ' => 'O', 'ớ' => 'o',
|
||||
'Ứ' => 'U', 'ứ' => 'u',
|
||||
// dot below
|
||||
'Ạ' => 'A', 'ạ' => 'a',
|
||||
'Ậ' => 'A', 'ậ' => 'a',
|
||||
'Ặ' => 'A', 'ặ' => 'a',
|
||||
'Ẹ' => 'E', 'ẹ' => 'e',
|
||||
'Ệ' => 'E', 'ệ' => 'e',
|
||||
'Ị' => 'I', 'ị' => 'i',
|
||||
'Ọ' => 'O', 'ọ' => 'o',
|
||||
'Ộ' => 'O', 'ộ' => 'o',
|
||||
'Ợ' => 'O', 'ợ' => 'o',
|
||||
'Ụ' => 'U', 'ụ' => 'u',
|
||||
'Ự' => 'U', 'ự' => 'u',
|
||||
'Ỵ' => 'Y', 'ỵ' => 'y',
|
||||
// Vowels with diacritic (Chinese, Hanyu Pinyin)
|
||||
'ɑ' => 'a',
|
||||
// macron
|
||||
'Ǖ' => 'U', 'ǖ' => 'u',
|
||||
// acute accent
|
||||
'Ǘ' => 'U', 'ǘ' => 'u',
|
||||
// caron
|
||||
'Ǎ' => 'A', 'ǎ' => 'a',
|
||||
'Ǐ' => 'I', 'ǐ' => 'i',
|
||||
'Ǒ' => 'O', 'ǒ' => 'o',
|
||||
'Ǔ' => 'U', 'ǔ' => 'u',
|
||||
'Ǚ' => 'U', 'ǚ' => 'u',
|
||||
// grave accent
|
||||
'Ǜ' => 'U', 'ǜ' => 'u',
|
||||
);
|
||||
|
||||
$string = strtr($string, $chars);
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
@@ -14,6 +14,10 @@ class BandcampBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://s4.bcbits.com/img/bc_favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('No results for this query.');
|
||||
|
@@ -7,6 +7,10 @@ class BlaguesDeMerdeBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Blagues De Merde';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/img/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
|
@@ -31,6 +31,10 @@ class BloombergBridge extends BridgeAbstract
|
||||
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) {
|
||||
|
@@ -27,6 +27,10 @@ class BundesbankBridge extends BridgeAbstract {
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'resource/crblob/1890/a7f48ee0ae35348748121770ba3ca009/mL/favicon-ico-data.ico';
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch($this->getInput(self::PARAM_LANG)) {
|
||||
case self::LANG_EN: return self::URI . 'en/publications/reports/studies';
|
||||
|
@@ -52,9 +52,9 @@ class CNETBridge extends BridgeAbstract {
|
||||
returnClientError('Invalid topic: ' . $topic);
|
||||
|
||||
// Retrieve webpage
|
||||
$pageUrl = self::URI . (empty($topic) ? 'news/' : $topic.'/');
|
||||
$pageUrl = self::URI . (empty($topic) ? 'news/' : $topic . '/');
|
||||
$html = getSimpleHTMLDOM($pageUrl)
|
||||
or returnServerError('Could not request CNET: '.$pageUrl);
|
||||
or returnServerError('Could not request CNET: ' . $pageUrl);
|
||||
|
||||
// Process articles
|
||||
foreach($html->find('div.assetBody, div.riverPost') as $element) {
|
||||
|
@@ -7,6 +7,9 @@ class ChristianDailyReporterBridge extends BridgeAbstract {
|
||||
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/';
|
||||
|
@@ -3,7 +3,7 @@ class CommonDreamsBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'nyutag';
|
||||
const NAME = 'CommonDreams Bridge';
|
||||
const URI = 'http://www.commondreams.org/';
|
||||
const URI = 'https://www.commondreams.org/';
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
public function collectData(){
|
||||
|
@@ -32,6 +32,10 @@ class ContainerLinuxReleasesBridge extends BridgeAbstract {
|
||||
return json_decode($json, true);
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://coreos.com/assets/ico/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$data = $this->getReleaseFeed($this->getJsonUri());
|
||||
|
||||
|
211
bridges/CrewbayBridge.php
Normal file
211
bridges/CrewbayBridge.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?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->getAttribute('data-modal-href'));
|
||||
|
||||
$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'));
|
||||
|
||||
if ($this->getInput('type') == 'boats') {
|
||||
$fields = array('job', 'boat', 'skipper');
|
||||
} else {
|
||||
$fields = array('profile', 'positions', 'info', 'qualifications' , 'skills', 'references');
|
||||
}
|
||||
|
||||
$content = '';
|
||||
foreach ($fields as $field) {
|
||||
$info = $htmlDetail->find('.profile--modal-body .info-' . $field, 0);
|
||||
if ($info) {
|
||||
$content .= $htmlDetail->find('.profile--modal-body .info-' . $field, 0)->innertext;
|
||||
}
|
||||
}
|
||||
|
||||
$item['content'] = $content;
|
||||
|
||||
if (!empty($this->getInput('keyword'))) {
|
||||
$keyword = strtolower($this->getInput('keyword'));
|
||||
if (strpos(strtolower($item['title']), $keyword) === false) {
|
||||
if (strpos(strtolower($content), $keyword) === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
$positions = explode("\r\n", trim($htmlDetail->find('.info-positions .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;
|
||||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
@@ -48,6 +48,10 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static1-ssl.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vf806ca4ed0deed812';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$limit = 5;
|
||||
|
@@ -1074,10 +1074,10 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
$url = $this->i8n('bridge-uri')
|
||||
. '/search/advanced?q='
|
||||
. urlencode($q)
|
||||
. '&hide_expired='. $hide_expired
|
||||
. '&hide_local='. $hide_local
|
||||
. '&priceFrom='. $priceFrom
|
||||
. '&priceTo='. $priceTo
|
||||
. '&hide_expired=' . $hide_expired
|
||||
. '&hide_local=' . $hide_local
|
||||
. '&priceFrom=' . $priceFrom
|
||||
. '&priceTo=' . $priceTo
|
||||
/* Some default parameters
|
||||
* search_fields : Search in Titres & Descriptions & Codes
|
||||
* sort_by : Sort the search by new deals
|
||||
@@ -1152,30 +1152,30 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
foreach ($list as $deal) {
|
||||
$item = array();
|
||||
$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;
|
||||
$item['author'] = $deal->find('span.thread-username', 0)->plaintext;
|
||||
$item['content'] = '<table><tr><td><a href="'
|
||||
. $deal->find(
|
||||
'a[class*='. $selectorImageLink .']', 0)->href
|
||||
'a[class*=' . $selectorImageLink . ']', 0)->href
|
||||
. '"><img src="'
|
||||
. $this->getImage($deal)
|
||||
. '"/></td><td><h2><a href="'
|
||||
. $deal->find('a[class*='. $selectorLink .']', 0)->href
|
||||
. $deal->find('a[class*=' . $selectorLink . ']', 0)->href
|
||||
. '">'
|
||||
. $deal->find('a[class*='. $selectorLink .']', 0)->innertext
|
||||
. $deal->find('a[class*=' . $selectorLink . ']', 0)->innertext
|
||||
. '</a></h2>'
|
||||
. $this->getPrice($deal)
|
||||
. $this->getDiscount($deal)
|
||||
. $this->getShipsFrom($deal)
|
||||
. $this->getShippingCost($deal)
|
||||
. $this->GetSource($deal)
|
||||
. $deal->find('div[class*='. $selectorDescription .']', 0)->innertext
|
||||
. $deal->find('div[class*=' . $selectorDescription . ']', 0)->innertext
|
||||
. '</td><td>'
|
||||
. $deal->find('div[class*='. $selectorHot .']', 0)
|
||||
. $deal->find('div[class*=' . $selectorHot . ']', 0)
|
||||
->find('span', 1)->outertext
|
||||
. '</td></table>';
|
||||
$dealDateDiv = $deal->find('div[class*='. $selectorDate .']', 0)
|
||||
$dealDateDiv = $deal->find('div[class*=' . $selectorDate . ']', 0)
|
||||
->find('span[class=hide--toW3]');
|
||||
$itemDate = end($dealDateDiv)->plaintext;
|
||||
// In case of a Local deal, there is no date, but we can use
|
||||
@@ -1214,7 +1214,7 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
{
|
||||
if ($deal->find(
|
||||
'span[class*=thread-price]', 0) != null) {
|
||||
return '<div>'.$this->i8n('price') .' : '
|
||||
return '<div>' . $this->i8n('price') . ' : '
|
||||
. $deal->find(
|
||||
'span[class*=thread-price]', 0
|
||||
)->plaintext
|
||||
@@ -1233,11 +1233,11 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
{
|
||||
if ($deal->find('span[class*=cept-shipping-price]', 0) != null) {
|
||||
if ($deal->find('span[class*=cept-shipping-price]', 0)->children(0) != null) {
|
||||
return '<div>'. $this->i8n('shipping') .' : '
|
||||
return '<div>' . $this->i8n('shipping') . ' : '
|
||||
. $deal->find('span[class*=cept-shipping-price]', 0)->children(0)->innertext
|
||||
. '</div>';
|
||||
} else {
|
||||
return '<div>'. $this->i8n('shipping') .' : '
|
||||
return '<div>' . $this->i8n('shipping') . ' : '
|
||||
. $deal->find('span[class*=cept-shipping-price]', 0)->innertext
|
||||
. '</div>';
|
||||
}
|
||||
@@ -1253,7 +1253,7 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
private function GetSource($deal)
|
||||
{
|
||||
if ($deal->find('a[class=text--color-greyShade]', 0) != null) {
|
||||
return '<div>'. $this->i8n('origin') .' : '
|
||||
return '<div>' . $this->i8n('origin') . ' : '
|
||||
. $deal->find('a[class=text--color-greyShade]', 0)->outertext
|
||||
. '</div>';
|
||||
} else {
|
||||
@@ -1274,7 +1274,7 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
} else {
|
||||
$discount = '';
|
||||
}
|
||||
return '<div>'. $this->i8n('discount') .' : <span style="text-decoration: line-through;">'
|
||||
return '<div>' . $this->i8n('discount') . ' : <span style="text-decoration: line-through;">'
|
||||
. $deal->find(
|
||||
'span[class*=mute--text text--lineThrough]', 0
|
||||
)->plaintext
|
||||
@@ -1315,13 +1315,13 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
'cept-thread-img'
|
||||
)
|
||||
);
|
||||
if ($deal->find('img[class='. $selectorLazy .']', 0) != null) {
|
||||
if ($deal->find('img[class=' . $selectorLazy . ']', 0) != null) {
|
||||
return json_decode(
|
||||
html_entity_decode(
|
||||
$deal->find('img[class='. $selectorLazy .']', 0)
|
||||
$deal->find('img[class=' . $selectorLazy . ']', 0)
|
||||
->getAttribute('data-lazy-img')))->{'src'};
|
||||
} else {
|
||||
return $deal->find('img[class*='. $selectorPlain .']', 0 )->src;
|
||||
return $deal->find('img[class*=' . $selectorPlain . ']', 0 )->src;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1340,9 +1340,9 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
'text--color-greyShade'
|
||||
)
|
||||
);
|
||||
if ($deal->find('span[class='. $selector .']', 0) != null) {
|
||||
if ($deal->find('span[class=' . $selector . ']', 0) != null) {
|
||||
return '<div>'
|
||||
. $deal->find('span[class='. $selector .']', 0)->children(2)->plaintext
|
||||
. $deal->find('span[class=' . $selector . ']', 0)->children(2)->plaintext
|
||||
. '</div>';
|
||||
} else {
|
||||
return '';
|
||||
@@ -1445,12 +1445,12 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
public function getName(){
|
||||
switch($this->queriedContext) {
|
||||
case $this->i8n('context-keyword'):
|
||||
return $this->i8n('bridge-name') . ' - '. $this->i8n('title-keyword') .' : '. $this->getInput('q');
|
||||
return $this->i8n('bridge-name') . ' - ' . $this->i8n('title-keyword') . ' : ' . $this->getInput('q');
|
||||
break;
|
||||
case $this->i8n('context-group'):
|
||||
$values = $this->getParameters()[$this->i8n('context-group')]['group']['values'];
|
||||
$group = array_search($this->getInput('group'), $values);
|
||||
return $this->i8n('bridge-name') . ' - '. $this->i8n('title-group'). ' : '. $group;
|
||||
return $this->i8n('bridge-name') . ' - ' . $this->i8n('title-group') . ' : ' . $group;
|
||||
break;
|
||||
default: // Return default value
|
||||
return static::NAME;
|
||||
|
@@ -75,6 +75,10 @@ class DiceBridge extends BridgeAbstract {
|
||||
),
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://assets.dice.com/techpro/img/favicons/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$uri = 'https://www.dice.com/jobs/advancedResult.html';
|
||||
$uri .= '?for_one=' . urlencode($this->getInput('for_one'));
|
||||
|
@@ -81,7 +81,7 @@ class DiscogsBridge extends BridgeAbstract {
|
||||
. $this->getInput('username_folder')
|
||||
. '/collection/folders/'
|
||||
. $this->getInput('folderid')
|
||||
.'/releases?sort=added&sort_order=desc')
|
||||
. '/releases?sort=added&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
$jsonData = json_decode($data, true)['releases'];
|
||||
}
|
||||
|
@@ -7,6 +7,11 @@ class DribbbleBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 1800;
|
||||
const DESCRIPTION = 'Returns the newest popular shots from Dribbble.';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://cdn.dribbble.com/assets/
|
||||
favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . '/shots')
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
@@ -99,7 +99,7 @@ class ETTVBridge extends BridgeAbstract {
|
||||
public function collectData(){
|
||||
// No control on inputs, because all defaultValue are set
|
||||
$query_str = 'torrents-search.php';
|
||||
$query_str .= '?search=' . urlencode('+'.str_replace(' ', ' +', $this->getInput('query')));
|
||||
$query_str .= '?search=' . urlencode('+' . str_replace(' ', ' +', $this->getInput('query')));
|
||||
$query_str .= '&cat=' . $this->getInput('cat');
|
||||
$query_str .= '&incldead=' . $this->getInput('status');
|
||||
$query_str .= '&lang=' . $this->getInput('lang');
|
||||
|
@@ -7,6 +7,11 @@ class EliteDangerousGalnetBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Returns the latest page of news from Galnet';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://community.elitedangerous.com/sites/
|
||||
EDSITE_COMM/themes/bootstrap/bootstrap_community/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
@@ -121,7 +121,7 @@ class ElloBridge extends BridgeAbstract {
|
||||
|
||||
private function getAPIKey() {
|
||||
$cache = Cache::create('FileCache');
|
||||
$cache->setPath(CACHE_DIR);
|
||||
$cache->setPath(PATH_CACHE);
|
||||
$cache->setParameters(['key']);
|
||||
$key = $cache->loadData();
|
||||
|
||||
|
@@ -57,6 +57,10 @@ class ElsevierBridge extends BridgeAbstract {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://cdn.elsevier.io/verona/includes/favicons/favicon-32x32.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$uri = self::URI . $this->getInput('j') . '/recent-articles/';
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
|
@@ -17,7 +17,7 @@ class EtsyBridge extends BridgeAbstract {
|
||||
'queryextension' => array(
|
||||
'name' => 'Query extension',
|
||||
'type' => 'text',
|
||||
'requied' => false,
|
||||
'required' => false,
|
||||
'title' => 'Insert additional query parts here
|
||||
(anything after ?search=<your search query>)',
|
||||
'exampleValue' => '&explicit=1&locationQuery=2921044'
|
||||
@@ -25,9 +25,9 @@ class EtsyBridge extends BridgeAbstract {
|
||||
'showimage' => array(
|
||||
'name' => 'Show image in content',
|
||||
'type' => 'checkbox',
|
||||
'requrired' => false,
|
||||
'required' => false,
|
||||
'title' => 'Activate to show the image in the content',
|
||||
'defaultValue' => false
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -36,26 +36,27 @@ class EtsyBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Failed to receive ' . $this->getURI());
|
||||
|
||||
$results = $html->find('div.block-grid-item');
|
||||
$results = $html->find('li.block-grid-item');
|
||||
|
||||
foreach($results as $result) {
|
||||
// Skip banner cards (ads for categories)
|
||||
if($result->find('a.banner-card'))
|
||||
if($result->find('span.ad-indicator'))
|
||||
continue;
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $result->find('a', 0)->title;
|
||||
$item['uri'] = $result->find('a', 0)->href;
|
||||
$item['author'] = $result->find('div.card-shop-name', 0)->plaintext;
|
||||
$item['author'] = $result->find('p.text-gray-lighter', 0)->plaintext;
|
||||
|
||||
$item['content'] = '<p>'
|
||||
. $result->find('div.card-price', 0)->plaintext
|
||||
. $result->find('span.currency-value', 0)->plaintext . ' '
|
||||
. $result->find('span.currency-symbol', 0)->plaintext
|
||||
. '</p><p>'
|
||||
. $result->find('div.card-title', 0)->plaintext
|
||||
. $result->find('a', 0)->title
|
||||
. '</p>';
|
||||
|
||||
$image = $result->find('img.placeholder', 0)->src;
|
||||
$image = $result->find('img.display-block', 0)->src;
|
||||
|
||||
if($this->getInput('showimage')) {
|
||||
$item['content'] .= '<img src="' . $image . '">';
|
||||
|
@@ -15,6 +15,10 @@ class FB2Bridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static.xx.fbcdn.net/rsrc.php/yo/r/iRmz9lCMBD2.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
//Utility function for cleaning a Facebook link
|
||||
@@ -65,14 +69,14 @@ class FB2Bridge extends BridgeAbstract {
|
||||
if($this->getInput('u') !== null) {
|
||||
$page = 'https://touch.facebook.com/' . $this->getInput('u');
|
||||
$cookies = $this->getCookies($page);
|
||||
$pageID = $this->getPageID($page, $cookies);
|
||||
$pageInfo = $this->getPageInfos($page, $cookies);
|
||||
|
||||
if($pageID === null) {
|
||||
if($pageInfo['userId'] === null) {
|
||||
echo <<<EOD
|
||||
Unable to get the page id. You should consider getting the ID by hand, then importing it into FB2Bridge
|
||||
EOD;
|
||||
die();
|
||||
} elseif($pageID == -1) {
|
||||
} elseif($pageInfo['userId'] == -1) {
|
||||
echo <<<EOD
|
||||
This page is not accessible without being logged in.
|
||||
EOD;
|
||||
@@ -81,24 +85,31 @@ EOD;
|
||||
}
|
||||
|
||||
//Build the string for the first request
|
||||
$requestString = 'https://touch.facebook.com/pages_reaction_units/more/?page_id='
|
||||
. $pageID
|
||||
. '&cursor={"card_id"%3A"videos"%2C"has_next_page"%3Atrue}&surface=mobile_page_home&unit_count=8';
|
||||
|
||||
$requestString = 'https://touch.facebook.com/page_content_list_view/more/?page_id='
|
||||
. $pageInfo['userId']
|
||||
. '&start_cursor=1&num_to_fetch=105&surface_type=timeline';
|
||||
$fileContent = getContents($requestString);
|
||||
|
||||
$articleIndex = 0;
|
||||
$maxArticle = 3;
|
||||
|
||||
$html = $this->buildContent($fileContent);
|
||||
$author = $this->getInput('u');
|
||||
$author = $pageInfo['username'];
|
||||
|
||||
foreach($html->find('article') as $content) {
|
||||
|
||||
$item = array();
|
||||
//echo $content; die();
|
||||
preg_match('/publish_time\\\":([0-9]+),/', $content->getAttribute('data-store', 0), $match);
|
||||
if(isset($match[1]))
|
||||
$timestamp = $match[1];
|
||||
else
|
||||
$timestamp = 0;
|
||||
|
||||
$item['uri'] = 'http://touch.facebook.com'
|
||||
. $content->find("div[class='_52jc _5qc4 _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href');
|
||||
$item['uri'] = html_entity_decode('http://touch.facebook.com'
|
||||
. $content->find("div[class='_52jc _5qc4 _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href'), ENT_QUOTES);
|
||||
|
||||
//Decode images
|
||||
$imagecleaned = preg_replace_callback('/<i [^>]* style="[^"]*url\(\'(.*?)\'\).*?><\/i>/m', function ($matches) {
|
||||
return "<img src='" . str_replace(['\\3a ', '\\3d ', '\\26 '], [':', '=', '&'], $matches[1]) . "' />";
|
||||
}, $content);
|
||||
$content = str_get_html($imagecleaned);
|
||||
|
||||
if($content->find('header', 0) !== null) {
|
||||
$content->find('header', 0)->innertext = '';
|
||||
@@ -108,8 +119,13 @@ EOD;
|
||||
$content->find('footer', 0)->innertext = '';
|
||||
}
|
||||
|
||||
// Replace emoticon images by their textual representation (part of the span)
|
||||
foreach($content->find('span[title*="emoticon"]') as $emoticon) {
|
||||
$emoticon->innertext = $emoticon->find('span[aria-hidden="true"]', 0)->innertext;
|
||||
}
|
||||
|
||||
//Remove html nodes, keep only img, links, basic formatting
|
||||
$content = strip_tags($content, '<a><img><i><u><br><p><h3><h4>');
|
||||
$content = strip_tags($content, '<a><img><i><u><br><p><h3><h4><section>');
|
||||
|
||||
//Adapt link hrefs: convert relative links into absolute links and bypass external link redirection
|
||||
$content = preg_replace_callback('/ href=\"([^"]+)\"/i', $unescape_fb_link, $content);
|
||||
@@ -122,7 +138,6 @@ EOD;
|
||||
'ajaxify',
|
||||
'tabindex',
|
||||
'class',
|
||||
'style',
|
||||
'data-[^=]*',
|
||||
'aria-[^=]*',
|
||||
'role',
|
||||
@@ -135,7 +150,36 @@ EOD;
|
||||
// "<i><u>smile emoticon</u></i>" back to ASCII emoticons eg ":)"
|
||||
$content = preg_replace_callback('/<i><u>([^ <>]+) ([^<>]+)<\/u><\/i>/i', $unescape_fb_emote, $content);
|
||||
|
||||
$item['content'] = $content;
|
||||
//Remove the "...Plus" tag
|
||||
$content = preg_replace(
|
||||
'/… (<span>|)<a href="https:\/\/www\.facebook\.com\/story\.php\?story_fbid=.*?<\/a>/m',
|
||||
'', $content, 1);
|
||||
|
||||
//Remove tracking images
|
||||
$content = preg_replace('/<img src=\'.*?safe_image\.php.*?\' \/>/m', '', $content);
|
||||
|
||||
//Remove the double section tags
|
||||
$content = str_replace(['<section><section>', '</section></section>'], ['<section>', '</section>'], $content);
|
||||
|
||||
//Move the section tag link upper, if it is down
|
||||
$content = str_get_html($content);
|
||||
$sectionContent = $content->find('section', 0);
|
||||
if($sectionContent != null) {
|
||||
$sectionLink = $sectionContent->nextSibling();
|
||||
if($sectionLink != null) {
|
||||
$fullLink = '<a href="' . $sectionLink->getAttribute('href') . '">' . $sectionContent->innertext . '</a>';
|
||||
$sectionContent->innertext = $fullLink;
|
||||
}
|
||||
}
|
||||
|
||||
//Move the href tag upper if it is inside the section
|
||||
foreach($content->find('section > a') as $sectionToFix) {
|
||||
$sectionLink = $sectionToFix->getAttribute('href');
|
||||
$section = $sectionToFix->parent();
|
||||
$section->outertext = '<a href="' . $sectionLink . '">' . $section . '</a>';
|
||||
}
|
||||
|
||||
$item['content'] = html_entity_decode($content, ENT_QUOTES);
|
||||
|
||||
$title = $author;
|
||||
if (strlen($title) > 24)
|
||||
@@ -144,57 +188,29 @@ EOD;
|
||||
if (strlen($title) > 64)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
|
||||
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['title'] = html_entity_decode($title, ENT_QUOTES);
|
||||
$item['author'] = html_entity_decode($author, ENT_QUOTES);
|
||||
$item['timestamp'] = html_entity_decode($timestamp, ENT_QUOTES);
|
||||
|
||||
array_push($this->items, $item);
|
||||
if($item['timestamp'] != 0)
|
||||
array_push($this->items, $item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Currently not used. Is used to get more than only 3 elements, as they appear on another page.
|
||||
private function computeNextLink($string, $pageID){
|
||||
|
||||
$regex = implode(
|
||||
'',
|
||||
array(
|
||||
'/timeline_unit',
|
||||
"\\\\\\\\u00253A1",
|
||||
"\\\\\\\\u00253A([0-9]*)",
|
||||
"\\\\\\\\u00253A([0-9]*)",
|
||||
"\\\\\\\\u00253A([0-9]*)",
|
||||
"\\\\\\\\u00253A([0-9]*)/"
|
||||
)
|
||||
);
|
||||
|
||||
preg_match($regex, $string, $result);
|
||||
|
||||
return implode(
|
||||
'',
|
||||
array(
|
||||
'https://touch.facebook.com/pages_reaction_units/more/?page_id=',
|
||||
$pageID,
|
||||
'&cursor=%7B%22timeline_cursor%22%3A%22timeline_unit%3A1%3A',
|
||||
$result[1],
|
||||
'%3A',
|
||||
$result[2],
|
||||
'%3A',
|
||||
$result[3],
|
||||
'%3A',
|
||||
$result[4],
|
||||
'%22%2C%22timeline_section_cursor%22%3A%7B%7D%2C%22',
|
||||
'has_next_page%22%3Atrue%7D&surface=mobile_page_home&unit_count=3'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
//Builds the HTML from the encoded JS that Facebook provides.
|
||||
private function buildContent($pageContent){
|
||||
// The html ends with:
|
||||
// /div>","replaceifexists
|
||||
$regex = '/\\"html\\":(\".+\/div>"),"replace/';
|
||||
preg_match($regex, $pageContent, $result);
|
||||
return str_get_html(html_entity_decode(json_decode($result[1])));
|
||||
|
||||
$htmlContent = json_decode($result[1]);
|
||||
$htmlContent = preg_replace('/(?<!style)="(.*?)"/', '=\'$1\'', $htmlContent);
|
||||
$htmlContent = html_entity_decode($htmlContent, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return str_get_html($htmlContent);
|
||||
}
|
||||
|
||||
|
||||
@@ -224,8 +240,8 @@ EOD;
|
||||
return substr($cookies, 1);
|
||||
}
|
||||
|
||||
//Get the page ID from the Facebook page.
|
||||
private function getPageID($page, $cookies){
|
||||
//Get the page ID and username from the Facebook page.
|
||||
private function getPageInfos($page, $cookies){
|
||||
|
||||
$context = stream_context_create(array(
|
||||
'http' => array(
|
||||
@@ -241,19 +257,28 @@ EOD;
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Get the username
|
||||
$usernameRegex = '/data-nt=\"FB:TEXT4\">(.*?)<\/div>/m';
|
||||
preg_match($usernameRegex, $pageContent, $usernameMatches);
|
||||
if(count($usernameMatches) > 0) {
|
||||
$username = strip_tags($usernameMatches[1]);
|
||||
} else {
|
||||
$username = $this->getInput('u');
|
||||
}
|
||||
|
||||
//Get the page ID if we don't have a captcha
|
||||
$regex = '/page_id=([0-9]*)&/';
|
||||
preg_match($regex, $pageContent, $matches);
|
||||
|
||||
if(count($matches) > 0) {
|
||||
return $matches[1];
|
||||
return array('userId' => $matches[1], 'username' => $username);
|
||||
}
|
||||
|
||||
//Get the page ID if we do have a captcha
|
||||
$regex = '/"pageID":"([0-9]*)"/';
|
||||
preg_match($regex, $pageContent, $matches);
|
||||
|
||||
return $matches[1];
|
||||
return array('userId' => $matches[1], 'username' => $username);
|
||||
|
||||
}
|
||||
|
||||
|
@@ -19,6 +19,10 @@ class FDroidBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/favicon.ico?v=8j6PKzW9Mk';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$url = self::URI;
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
@@ -45,9 +49,9 @@ class FDroidBridge extends BridgeAbstract {
|
||||
$item['icon'] = $element->find('img', 0)->src;
|
||||
$item['summary'] = $element->find('span.package-summary', 0)->plaintext;
|
||||
$item['content'] = '
|
||||
<a href="'.$item['uri'].'">
|
||||
<img alt="" style="max-height:128px" src="'.$item['icon'].'">
|
||||
</a><br>'.$item['summary'];
|
||||
<a href="' . $item['uri'] . '">
|
||||
<img alt="" style="max-height:128px" src="' . $item['icon'] . '">
|
||||
</a><br>' . $item['summary'];
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -56,6 +56,10 @@ class FacebookBridge extends BridgeAbstract {
|
||||
private $authorName = '';
|
||||
private $groupName = '';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static.xx.fbcdn.net/rsrc.php/yo/r/iRmz9lCMBD2.ico';
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
|
||||
switch($this->queriedContext) {
|
||||
@@ -95,7 +99,7 @@ class FacebookBridge extends BridgeAbstract {
|
||||
$user = $this->sanitizeUser($this->getInput('u'));
|
||||
|
||||
if(!strpos($user, '/')) {
|
||||
$uri .= '/pg/' . urlencode($user) . '/posts';
|
||||
$uri .= urlencode($user) . '/posts';
|
||||
} else {
|
||||
$uri .= 'pages/' . $user;
|
||||
}
|
||||
@@ -360,6 +364,26 @@ class FacebookBridge extends BridgeAbstract {
|
||||
}, $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.
|
||||
* i.e. "<i><u>smile emoticon</u></i>" => ":)"
|
||||
@@ -422,8 +446,7 @@ class FacebookBridge extends BridgeAbstract {
|
||||
// Show captcha filling form to the viewer, proxying the captcha image
|
||||
$img = base64_encode(getContents($captcha->find('img', 0)->src));
|
||||
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/html');
|
||||
header('Content-Type: text/html', true, 500);
|
||||
|
||||
$message = <<<EOD
|
||||
<form method="post" action="?{$_SERVER['QUERY_STRING']}">
|
||||
@@ -506,8 +529,6 @@ EOD;
|
||||
returnServerError('You must be logged in to view this page. This is not supported by RSS-Bridge.');
|
||||
}
|
||||
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$element = $html
|
||||
->find('#pagelet_timeline_main_column')[0]
|
||||
->children(0)
|
||||
@@ -517,7 +538,7 @@ EOD;
|
||||
|
||||
if(isset($element)) {
|
||||
|
||||
$author = str_replace(' | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
|
||||
$author = str_replace(' - Posts | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
|
||||
|
||||
$profilePic = $html->find('meta[property="og:image"]', 0)->content;
|
||||
|
||||
@@ -554,16 +575,30 @@ EOD;
|
||||
|
||||
if(count($post->find('abbr')) > 0) {
|
||||
|
||||
//Retrieve post contents
|
||||
$content = preg_replace(
|
||||
'/(?i)><div class=\"clearfix([^>]+)>(.+?)div\ class=\"userContent\"/i',
|
||||
'',
|
||||
$post);
|
||||
$content = $post->find('.userContentWrapper', 0);
|
||||
|
||||
$content = preg_replace(
|
||||
'/(?i)><div class=\"_59tj([^>]+)>(.+?)<\/div><\/div><a/i',
|
||||
'',
|
||||
$content);
|
||||
// This array specifies filters applied to all posts in order of appearance
|
||||
$content_filters = array(
|
||||
'._5mly', // Remove embedded videos (the preview image remains)
|
||||
'._2ezg', // Remove "Views ..."
|
||||
'.hidden_elem', // Remove hidden elements (they are hidden anyway)
|
||||
);
|
||||
|
||||
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(
|
||||
'/(?i)><div class=\"_3dp([^>]+)>(.+?)div\ class=\"[^u]+userContent\"/i',
|
||||
@@ -607,6 +642,14 @@ EOD;
|
||||
|
||||
$this->unescape_fb_emote($content);
|
||||
|
||||
// Restore links in the post before further parsing
|
||||
$post = defaultLinkTo($post, self::URI);
|
||||
|
||||
// Restore links in the content before adding to the item
|
||||
$content = defaultLinkTo($content, self::URI);
|
||||
|
||||
$content = $this->remove_tracking_codes($content);
|
||||
|
||||
// Retrieve date of the post
|
||||
$date = $post->find('abbr')[0];
|
||||
|
||||
|
@@ -7,6 +7,10 @@ class FierPandaBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Returns latest articles from Fier Panda.';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'wp-content/themes/fier-panda/img/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
|
82
bridges/FindACrewBridge.php
Normal file
82
bridges/FindACrewBridge.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
class FindACrewBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'couraudt';
|
||||
const NAME = 'Find A Crew Bridge';
|
||||
const URI = 'https://www.findacrew.net';
|
||||
const DESCRIPTION = 'Returns the newest sailing offers.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'type' => array(
|
||||
'name' => 'Type of search',
|
||||
'title' => 'Choose between finding a boat or a crew',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Find a boat' => 'boat',
|
||||
'Find a crew' => 'crew'
|
||||
)
|
||||
),
|
||||
'long' => array(
|
||||
'name' => 'Longitude of the searched location',
|
||||
'title' => 'Center the search at that longitude (e.g: -42.02)'
|
||||
),
|
||||
'lat' => array(
|
||||
'name' => 'Latitude of the searched location',
|
||||
'title' => 'Center the search at that latitude (e.g: 12.42)'
|
||||
),
|
||||
'distance' => array(
|
||||
'name' => 'Limit boundary of search in KM',
|
||||
'title' => 'Boundary of the search in kilometers when using longitude and latitude'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getURI();
|
||||
|
||||
if ($this->getInput('type') == 'boat') {
|
||||
$data = array('SrhLstBtAction' => 'Create');
|
||||
} else {
|
||||
$data = array('SrhLstCwAction' => 'Create');
|
||||
}
|
||||
|
||||
if ($this->getInput('long') && $this->getInput('lat')) {
|
||||
$data['real_LocSrh_Lng'] = $this->getInput('long');
|
||||
$data['real_LocSrh_Lat'] = $this->getInput('lat');
|
||||
if ($this->getInput('distance')) {
|
||||
$data['LocDis'] = (int)$this->getInput('distance') * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
$header = array(
|
||||
'Content-Type: application/x-www-form-urlencoded'
|
||||
);
|
||||
|
||||
$opts = array(
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => http_build_query($data) . "\n"
|
||||
);
|
||||
|
||||
$html = getSimpleHTMLDOM($url, $header, $opts) or returnClientError('No results for this query.');
|
||||
|
||||
$annonces = $html->find('.css_SrhRst');
|
||||
foreach ($annonces as $annonce) {
|
||||
$item = array();
|
||||
|
||||
$img = parent::getURI() . $annonce->find('.css_LstPic img', 0)->getAttribute('src');
|
||||
$item['title'] = $annonce->find('.css_LstCtrls span', 0)->plaintext;
|
||||
$item['uri'] = parent::getURI() . $annonce->find('.css_PnlCtrls a', 0)->href;
|
||||
$content = $annonce->find('.css_LstDtl div', 2)->innertext;
|
||||
$item['content'] = "<img src='$img' /><br>$content";
|
||||
$item['enclosures'] = array($img);
|
||||
$item['categories'] = array($annonce->find('.css_AccLocCur', 0)->plaintext);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = parent::getURI();
|
||||
// Those params must be in the URL
|
||||
$uri .= '/en/' . $this->getInput('type') . '/search?srhtyp=srhrst&mdl=2';
|
||||
return $uri;
|
||||
}
|
||||
}
|
@@ -69,7 +69,7 @@ class FourchanBridge extends BridgeAbstract {
|
||||
. '" src="'
|
||||
. $item['imageThumb']
|
||||
. '" /></a><br>'
|
||||
.$item['content'];
|
||||
. $item['content'];
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ class GBAtempBridge extends BridgeAbstract {
|
||||
|
||||
private function cleanupPostContent($content, $site_url){
|
||||
$content = str_replace(':arrow:', '➤', $content);
|
||||
$content = str_replace('href="attachments/', 'href="'.$site_url.'attachments/', $content);
|
||||
$content = str_replace('href="attachments/', 'href="' . $site_url . 'attachments/', $content);
|
||||
$content = stripWithDelimiters($content, '<script', '</script>');
|
||||
return $content;
|
||||
}
|
||||
|
@@ -54,7 +54,7 @@ class GitHubGistBridge extends BridgeAbstract {
|
||||
DEFAULT_SPAN_TEXT)
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
$fileinfo = $html->find('[class="file-info"]', 0)
|
||||
or returnServerError('Could not find file info!');
|
||||
@@ -69,7 +69,7 @@ class GitHubGistBridge extends BridgeAbstract {
|
||||
|
||||
foreach($comments as $comment) {
|
||||
|
||||
$uri = $comment->find('a[href^=#gistcomment]', 0)
|
||||
$uri = $comment->find('a[href*=#gistcomment]', 0)
|
||||
or returnServerError('Could not find comment anchor!');
|
||||
|
||||
$title = $comment->find('div[class="unminimized-comment"] h3[class="timeline-comment-header-text"]', 0)
|
||||
@@ -86,7 +86,7 @@ class GitHubGistBridge extends BridgeAbstract {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $this->getURI() . $uri->href;
|
||||
$item['uri'] = $uri->href;
|
||||
$item['title'] = str_replace('commented', 'commented on', $title->plaintext);
|
||||
$item['timestamp'] = strtotime($datetime->datetime);
|
||||
$item['author'] = '<a href="' . $author->href . '">' . $author->plaintext . '</a>';
|
||||
|
@@ -22,6 +22,10 @@ class GooglePlusPostBridge extends BridgeAbstract{
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://ssl.gstatic.com/images/branding/product/ico/google_plus_alldp.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$username = $this->getInput('username');
|
||||
|
@@ -28,7 +28,7 @@ class GoogleSearchBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(self::URI
|
||||
. 'search?q='
|
||||
. urlencode($this->getInput('q'))
|
||||
.'&num=100&complete=0&tbs=qdr:y,sbd:1')
|
||||
. '&num=100&complete=0&tbs=qdr:y,sbd:1')
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
$emIsRes = $html->find('div[id=ires]', 0);
|
||||
|
@@ -3,7 +3,7 @@ class InstagramBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'pauder';
|
||||
const NAME = 'Instagram Bridge';
|
||||
const URI = 'https://instagram.com/';
|
||||
const URI = 'https://www.instagram.com/';
|
||||
const DESCRIPTION = 'Returns the newest images';
|
||||
|
||||
const PARAMETERS = array(
|
||||
@@ -19,6 +19,12 @@ class InstagramBridge extends BridgeAbstract {
|
||||
'required' => true
|
||||
)
|
||||
),
|
||||
array(
|
||||
'l' => array(
|
||||
'name' => 'location',
|
||||
'required' => true
|
||||
)
|
||||
),
|
||||
'global' => array(
|
||||
'media_type' => array(
|
||||
'name' => 'Media type',
|
||||
@@ -38,16 +44,18 @@ class InstagramBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
|
||||
if(!is_null($this->getInput('h')) && $this->getInput('media_type') == 'story') {
|
||||
returnClientError('Stories are not supported for hashtags!');
|
||||
if(is_null($this->getInput('u')) && $this->getInput('media_type') == 'story') {
|
||||
returnClientError('Stories are not supported for hashtags nor locations!');
|
||||
}
|
||||
|
||||
$data = $this->getInstagramJSON($this->getURI());
|
||||
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
$userMedia = $data->entry_data->ProfilePage[0]->graphql->user->edge_owner_to_timeline_media->edges;
|
||||
} else {
|
||||
} elseif(!is_null($this->getInput('h'))) {
|
||||
$userMedia = $data->entry_data->TagPage[0]->graphql->hashtag->edge_hashtag_to_media->edges;
|
||||
} elseif(!is_null($this->getInput('l'))) {
|
||||
$userMedia = $data->entry_data->LocationsPage[0]->graphql->location->edge_location_to_media->edges;
|
||||
}
|
||||
|
||||
foreach($userMedia as $media) {
|
||||
@@ -85,7 +93,7 @@ class InstagramBridge extends BridgeAbstract {
|
||||
$item['content'] = $data[0];
|
||||
$item['enclosures'] = $data[1];
|
||||
} else {
|
||||
$item['content'] = '<img src="' . htmlentities($media->display_url) . '" alt="'. $item['title'] . '" />';
|
||||
$item['content'] = '<img src="' . htmlentities($media->display_url) . '" alt="' . $item['title'] . '" />';
|
||||
$item['enclosures'] = array($media->display_url);
|
||||
}
|
||||
|
||||
@@ -101,16 +109,21 @@ class InstagramBridge extends BridgeAbstract {
|
||||
$mediaInfo = $data->entry_data->PostPage[0]->graphql->shortcode_media;
|
||||
|
||||
//Process the first element, that isn't in the node graph
|
||||
$caption = $mediaInfo->edge_media_to_caption->edges[0]->node->text;
|
||||
if (count($mediaInfo->edge_media_to_caption->edges) > 0) {
|
||||
$caption = $mediaInfo->edge_media_to_caption->edges[0]->node->text;
|
||||
} else {
|
||||
$caption = '';
|
||||
}
|
||||
|
||||
$enclosures = [$mediaInfo->display_url];
|
||||
$content = '<img src="' . htmlentities($mediaInfo->display_url) . '" alt="'. $caption . '" />';
|
||||
$content = '<img src="' . htmlentities($mediaInfo->display_url) . '" alt="' . $caption . '" />';
|
||||
|
||||
foreach($mediaInfo->edge_sidecar_to_children->edges as $media) {
|
||||
|
||||
$content .= '<img src="' . htmlentities($media->node->display_url) . '" alt="'. $caption . '" />';
|
||||
$enclosures[] = $media->node->display_url;
|
||||
|
||||
$display_url = $media->node->display_url;
|
||||
if(!in_array($display_url, $enclosures)) { // add only if not added yet
|
||||
$content .= '<img src="' . htmlentities($display_url) . '" alt="' . $caption . '" />';
|
||||
$enclosures[] = $display_url;
|
||||
}
|
||||
}
|
||||
|
||||
return [$content, $enclosures];
|
||||
@@ -139,11 +152,12 @@ class InstagramBridge extends BridgeAbstract {
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return self::URI . urlencode($this->getInput('u'));
|
||||
return self::URI . urlencode($this->getInput('u')) . '/';
|
||||
} elseif(!is_null($this->getInput('h'))) {
|
||||
return self::URI . 'explore/tags/' . urlencode($this->getInput('h'));
|
||||
} elseif(!is_null($this->getInput('l'))) {
|
||||
return self::URI . 'explore/locations/' . urlencode($this->getInput('l'));
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,10 @@ class JapanExpoBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://s.japan-expo.com/katana/images/JES073/favicons/paris.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
function frenchPubDateToTimestamp($date_to_parse) {
|
||||
|
@@ -36,6 +36,11 @@ class KATBridge extends BridgeAbstract {
|
||||
'name' => 'Only get results from Elite or Verified uploaders ?',
|
||||
),
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://statuskatcrco-631f.kxcdn.com/assets/images/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
function parseDateTimestamp($element){
|
||||
$guessedDate = strptime($element, '%d-%m-%Y %H:%M:%S');
|
||||
|
@@ -38,6 +38,10 @@ class KernelBugTrackerBridge extends BridgeAbstract {
|
||||
private $bugid = '';
|
||||
private $bugdesc = '';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . '/images/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$limit = $this->getInput('limit');
|
||||
$sorting = $this->getInput('sorting');
|
||||
|
@@ -9,7 +9,7 @@ class LWNprevBridge extends BridgeAbstract{
|
||||
private $editionTimeStamp;
|
||||
|
||||
function getURI(){
|
||||
return self::URI.'free/bigpage';
|
||||
return self::URI . 'free/bigpage';
|
||||
}
|
||||
|
||||
private function jumpToNextTag(&$node){
|
||||
@@ -47,7 +47,7 @@ class LWNprevBridge extends BridgeAbstract{
|
||||
<html><head><title>LWN</title></head><body>{$content}</body></html>
|
||||
EOD;
|
||||
} else {
|
||||
$content = $content.'</body></html>';
|
||||
$content = $content . '</body></html>';
|
||||
}
|
||||
|
||||
libxml_use_internal_errors(true);
|
||||
@@ -172,7 +172,7 @@ EOD;
|
||||
|
||||
$prefix = '';
|
||||
if(!empty($cats[0])) {
|
||||
$prefix .= '['.$cats[0].($cats[1] ? '/'.$cats[1] : '').'] ';
|
||||
$prefix .= '[' . $cats[0] . ($cats[1] ? '/' . $cats[1] : '') . '] ';
|
||||
}
|
||||
return $prefix;
|
||||
}
|
||||
@@ -188,7 +188,7 @@ EOD;
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = self::URI.'#'.count($items);
|
||||
$item['uri'] = self::URI . '#' . count($items);
|
||||
|
||||
$item['timestamp'] = $this->editionTimeStamp;
|
||||
|
||||
@@ -197,7 +197,7 @@ EOD;
|
||||
$cat = $newsletters->previousSibling;
|
||||
$this->jumpToPreviousTag($cat);
|
||||
$prefix = $this->getItemPrefix($cat, $cats);
|
||||
$item['title'] = $prefix.' '.$newsletters->textContent;
|
||||
$item['title'] = $prefix . ' ' . $newsletters->textContent;
|
||||
|
||||
$node = $newsletters;
|
||||
$content = '';
|
||||
@@ -233,7 +233,7 @@ EOD;
|
||||
$cat = $cat->previousSibling;
|
||||
$this->jumpToPreviousTag($cat);
|
||||
$prefix = $this->getItemPrefix($cat, $cats);
|
||||
$item['title'] = $prefix.' '.$title->textContent;
|
||||
$item['title'] = $prefix . ' ' . $title->textContent;
|
||||
$items[] = array_merge($item, $this->getArticleContent($title));
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ EOD;
|
||||
$cat = $cat->previousSibling;
|
||||
$this->jumpToPreviousTag($cat);
|
||||
$prefix = $this->getItemPrefix($cat, $cats);
|
||||
$item['title'] = $prefix.' '.$title->textContent;
|
||||
$item['title'] = $prefix . ' ' . $title->textContent;
|
||||
$items[] = array_merge($item, $this->getArticleContent($title));
|
||||
}
|
||||
|
||||
|
@@ -38,6 +38,10 @@ class LegifranceJOBridge extends BridgeAbstract {
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.legifrance.gouv.fr/img/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or $this->returnServer('Unable to download ' . self::URI);
|
||||
|
@@ -117,7 +117,7 @@ class NineGagBridge extends BridgeAbstract {
|
||||
$cursor = 'c=10';
|
||||
$posts = array();
|
||||
for ($i = 0; $i < $this->getPages(); ++$i) {
|
||||
$content = getContents($url.$cursor);
|
||||
$content = getContents($url . $cursor);
|
||||
$json = json_decode($content, true);
|
||||
$posts = array_merge($posts, $json['data']['posts']);
|
||||
$cursor = $json['data']['nextCursor'];
|
||||
@@ -156,7 +156,7 @@ class NineGagBridge extends BridgeAbstract {
|
||||
$uri = $this->getInput('t');
|
||||
}
|
||||
|
||||
return self::URI.$uri;
|
||||
return self::URI . $uri;
|
||||
}
|
||||
|
||||
protected function getGroup() {
|
||||
|
@@ -26,6 +26,10 @@ class NotAlwaysBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'favicon_nar.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request NotAlways.');
|
||||
|
@@ -54,6 +54,10 @@ class NyaaTorrentsBridge extends BridgeAbstract {
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'static/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
// Build Search URL from user-provided parameters
|
||||
|
130
bridges/OnVaSortirBridge.php
Normal file
130
bridges/OnVaSortirBridge.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
class OnVaSortirBridge extends FeedExpander {
|
||||
const MAINTAINER = 'AntoineTurmel';
|
||||
const NAME = 'OnVaSortir';
|
||||
const URI = 'https://www.onvasortir.com';
|
||||
const DESCRIPTION = 'Returns the newest events from OnVaSortir (full text)';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'city' => array(
|
||||
'name' => 'City',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'values' => array(
|
||||
'Agen' => 'Agen',
|
||||
'Ajaccio' => 'Ajaccio',
|
||||
'Albi' => 'Albi',
|
||||
'Amiens' => 'Amiens',
|
||||
'Angers' => 'Angers',
|
||||
'Angoulême' => 'Angouleme',
|
||||
'Annecy' => 'annecy',
|
||||
'Aurillac' => 'aurillac',
|
||||
'Auxerre' => 'auxerre',
|
||||
'Avignon' => 'avignon',
|
||||
'Béziers' => 'Beziers',
|
||||
'Bastia' => 'Bastia',
|
||||
'Beauvais' => 'Beauvais',
|
||||
'Belfort' => 'Belfort',
|
||||
'Bergerac' => 'bergerac',
|
||||
'Besançon' => 'Besancon',
|
||||
'Biarritz' => 'Biarritz',
|
||||
'Blois' => 'Blois',
|
||||
'Bordeaux' => 'bordeaux',
|
||||
'Bourg-en-Bresse' => 'bourg-en-bresse',
|
||||
'Bourges' => 'Bourges',
|
||||
'Brest' => 'Brest',
|
||||
'Brive' => 'brive-la-gaillarde',
|
||||
'Bruxelles' => 'bruxelles',
|
||||
'Caen' => 'Caen',
|
||||
'Calais' => 'Calais',
|
||||
'Carcassonne' => 'Carcassonne',
|
||||
'Châteauroux' => 'Chateauroux',
|
||||
'Chalon-sur-saone' => 'chalon-sur-saone',
|
||||
'Chambéry' => 'chambery',
|
||||
'Chantilly' => 'chantilly',
|
||||
'Charleroi' => 'charleroi',
|
||||
'Charleville-Mézières' => 'Charleville-Mezieres',
|
||||
'Chartres' => 'Chartres',
|
||||
'Cherbourg' => 'Cherbourg',
|
||||
'Cholet' => 'cholet',
|
||||
'Clermont-Ferrand' => 'Clermont-Ferrand',
|
||||
'Compiègne' => 'compiegne',
|
||||
'Dieppe' => 'dieppe',
|
||||
'Dijon' => 'Dijon',
|
||||
'Dunkerque' => 'Dunkerque',
|
||||
'Evreux' => 'evreux',
|
||||
'Fréjus' => 'frejus',
|
||||
'Gap' => 'gap',
|
||||
'Genève' => 'geneve',
|
||||
'Grenoble' => 'Grenoble',
|
||||
'La Roche sur Yon' => 'La-Roche-sur-Yon',
|
||||
'La Rochelle' => 'La-Rochelle',
|
||||
'Lausanne' => 'lausanne',
|
||||
'Laval' => 'Laval',
|
||||
'Le Havre' => 'le-havre',
|
||||
'Le Mans' => 'le-mans',
|
||||
'Liège' => 'liege',
|
||||
'Lille' => 'lille',
|
||||
'Limoges' => 'Limoges',
|
||||
'Lorient' => 'Lorient',
|
||||
'Luxembourg' => 'Luxembourg',
|
||||
'Lyon' => 'lyon',
|
||||
'Marseille' => 'marseille',
|
||||
'Metz' => 'Metz',
|
||||
'Mons' => 'Mons',
|
||||
'Mont de Marsan' => 'mont-de-marsan',
|
||||
'Montauban' => 'Montauban',
|
||||
'Montluçon' => 'montlucon',
|
||||
'Montpellier' => 'montpellier',
|
||||
'Mulhouse' => 'Mulhouse',
|
||||
'Nîmes' => 'nimes',
|
||||
'Namur' => 'Namur',
|
||||
'Nancy' => 'Nancy',
|
||||
'Nantes' => 'nantes',
|
||||
'Nevers' => 'nevers',
|
||||
'Nice' => 'nice',
|
||||
'Niort' => 'niort',
|
||||
'Orléans' => 'orleans',
|
||||
'Périgueux' => 'perigueux',
|
||||
'Paris' => 'paris',
|
||||
'Pau' => 'Pau',
|
||||
'Perpignan' => 'Perpignan',
|
||||
'Poitiers' => 'Poitiers',
|
||||
'Quimper' => 'Quimper',
|
||||
'Reims' => 'Reims',
|
||||
'Rennes' => 'Rennes',
|
||||
'Roanne' => 'roanne',
|
||||
'Rodez' => 'rodez',
|
||||
'Rouen' => 'Rouen',
|
||||
'Saint-Brieuc' => 'Saint-Brieuc',
|
||||
'Saint-Etienne' => 'saint-etienne',
|
||||
'Saint-Malo' => 'saint-malo',
|
||||
'Saint-Nazaire' => 'saint-nazaire',
|
||||
'Saint-Quentin' => 'saint-quentin',
|
||||
'Saintes' => 'saintes',
|
||||
'Strasbourg' => 'Strasbourg',
|
||||
'Tarbes' => 'Tarbes',
|
||||
'Toulon' => 'Toulon',
|
||||
'Toulouse' => 'Toulouse',
|
||||
'Tours' => 'Tours',
|
||||
'Troyes' => 'troyes',
|
||||
'Valence' => 'valence',
|
||||
'Vannes' => 'vannes',
|
||||
'Zurich' => 'zurich',
|
||||
),
|
||||
'defaultValue' => ''
|
||||
)
|
||||
)
|
||||
);
|
||||
protected function parseItem($item){
|
||||
$item = parent::parseItem($item);
|
||||
$html = getSimpleHTMLDOMCached($item['uri']);
|
||||
$text = $html->find('div.corpsMax', 0)->innertext;
|
||||
$item['content'] = utf8_encode($text);
|
||||
return $item;
|
||||
}
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas('https://' .
|
||||
$this->getInput('city') . '.onvasortir.com/rss.php');
|
||||
}
|
||||
}
|
@@ -75,7 +75,7 @@ class PikabuBridge extends BridgeAbstract {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$img->outertext = '<img src="'.$src.'">';
|
||||
$img->outertext = '<img src="' . $src . '">';
|
||||
}
|
||||
|
||||
$categories = array();
|
||||
|
@@ -25,6 +25,10 @@ class PinterestBridge extends FeedExpander {
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://s.pinimg.com/webapp/style/images/favicon-9f8f9adf.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By username and board':
|
||||
|
@@ -18,7 +18,7 @@ class PixivBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getContents(static::URI.'search.php?word=' . urlencode($this->getInput('tag')))
|
||||
$html = getContents(static::URI . 'search.php?word=' . urlencode($this->getInput('tag')))
|
||||
or returnClientError('Unable to query pixiv.net');
|
||||
$regex = '/<input type="hidden"id="js-mount-point-search-result-list"data-items="([^"]*)/';
|
||||
$timeRegex = '/img\/([0-9]{4})\/([0-9]{2})\/([0-9]{2})\/([0-9]{2})\/([0-9]{2})\/([0-9]{2})\//';
|
||||
@@ -53,7 +53,7 @@ class PixivBridge extends BridgeAbstract {
|
||||
|
||||
$url = str_replace('_master1200', '', $url);
|
||||
$url = str_replace('c/240x240/img-master/', 'img-original/', $url);
|
||||
$path = CACHE_DIR . '/pixiv_img';
|
||||
$path = PATH_CACHE . '/pixiv_img';
|
||||
|
||||
if(!is_dir($path))
|
||||
mkdir($path, 0755, true);
|
||||
|
@@ -58,7 +58,7 @@ class RTBFBridge extends BridgeAbstract {
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('c'))) {
|
||||
return $this->getInput('c') .' - RTBF Bridge';
|
||||
return $this->getInput('c') . ' - RTBF Bridge';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
|
@@ -5,6 +5,10 @@ class RadioMelodieBridge extends BridgeAbstract {
|
||||
const DESCRIPTION = 'Retourne les actualités publiées par Radio Melodie';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'img/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . 'actu')
|
||||
or returnServerError('Could not request Radio Melodie.');
|
||||
@@ -23,7 +27,7 @@ class RadioMelodieBridge extends BridgeAbstract {
|
||||
$item['enclosures'] = array($pictureURL);
|
||||
$item['uri'] = self::URI . $element->parent()->href;
|
||||
$item['title'] = $element->find('h3', 0)->plaintext;
|
||||
$item['content'] = $element->find('p', 0)->plaintext . '<br/><img src="'.$pictureURL.'"/>';
|
||||
$item['content'] = $element->find('p', 0)->plaintext . '<br/><img src="' . $pictureURL . '"/>';
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -7,10 +7,14 @@ class RainbowSixSiegeBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Latest articles from the Rainbow Six Siege blog';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://ubistatic19-a.akamaihd.net/resource/en-us/game/rainbow6/siege-v3/r6s-favicon_316592.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$dlUrl = 'https://prod-tridionservice.ubisoft.com/live/v1/News/Latest?templateId=tcm%3A152-7677';
|
||||
$dlUrl .= '8-32&pageIndex=0&pageSize=10&language=en-US&detailPageId=tcm%3A152-194572-64';
|
||||
$dlUrl .= '&keywordList=175426&siteId=undefined&useSeoFriendlyUrl=true';
|
||||
$dlUrl .= '8-32&pageIndex=0&pageSize=10&language=en-US&detailPageId=tcm%3A150-194572-64';
|
||||
$dlUrl .= '&keywordList=233416%2C316144%2C233418%2C233417&siteId=undefined&useSeoFriendlyUrl=true';
|
||||
$jsonString = getContents($dlUrl) or returnServerError('Error while downloading the website content');
|
||||
|
||||
$json = json_decode($jsonString, true);
|
||||
|
@@ -1,25 +1,50 @@
|
||||
<?php
|
||||
class Rue89Bridge extends FeedExpander {
|
||||
class Rue89Bridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'pit-fgfjiudghdf';
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'Rue89';
|
||||
const URI = 'http://rue89.nouvelobs.com/';
|
||||
const DESCRIPTION = 'Returns the 5 newest posts from Rue89 (full text)';
|
||||
const URI = 'https://www.nouvelobs.com/rue89/';
|
||||
const DESCRIPTION = 'Returns the newest posts from Rue89';
|
||||
|
||||
protected function parseItem($item){
|
||||
$item = parent::parseItem($item);
|
||||
|
||||
$url = 'http://api.rue89.nouvelobs.com/export/mobile2/node/'
|
||||
. str_replace(' ', '', substr($item['uri'], -8))
|
||||
. '/full';
|
||||
public function collectData() {
|
||||
|
||||
$datas = json_decode(getContents($url), true);
|
||||
$item['content'] = $datas['node']['body'];
|
||||
$jsonArticles = getContents('https://appdata.nouvelobs.com/rue89/feed.json')
|
||||
or die('Unable to query Rue89 !');
|
||||
$articles = json_decode($jsonArticles)->items;
|
||||
foreach($articles as $article) {
|
||||
$this->items[] = $this->getArticle($article);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function getArticle($articleInfo) {
|
||||
|
||||
$articleJson = getContents($articleInfo->json_url) or die('Unable to get article !');
|
||||
$article = json_decode($articleJson);
|
||||
$item = array();
|
||||
$item['title'] = $article->title;
|
||||
$item['uri'] = $article->url;
|
||||
if($article->content_premium !== null) {
|
||||
$item['content'] = $article->content_premium;
|
||||
} else {
|
||||
$item['content'] = $article->content;
|
||||
}
|
||||
$item['timestamp'] = $article->date_publi;
|
||||
$item['author'] = $article->author->show_name;
|
||||
|
||||
$item['enclosures'] = array();
|
||||
foreach($article->images as $image) {
|
||||
$item['enclosures'][] = $image->url;
|
||||
}
|
||||
|
||||
$item['categories'] = array();
|
||||
foreach($article->categories as $category) {
|
||||
$item['categories'][] = $category->title;
|
||||
}
|
||||
|
||||
return $item;
|
||||
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas('http://api.rue89.nouvelobs.com/feed');
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,10 @@ class SupInfoBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . '/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
if(empty($this->getInput('tag'))) {
|
||||
|
@@ -25,7 +25,7 @@ class SuperSmashBlogBridge extends BridgeAbstract {
|
||||
|
||||
$video = $article['acf']['link_url'];
|
||||
if (strlen($video) != 0) {
|
||||
$video = str_get_html('<a href="' . $video .'">Youtube video</a>');
|
||||
$video = str_get_html('<a href="' . $video . '">Youtube video</a>');
|
||||
} else {
|
||||
$video = '';
|
||||
}
|
||||
|
@@ -14,6 +14,10 @@ class TagBoardBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static.tagboard.com/public/favicon-32x32.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$link = 'https://post-cache.tagboard.com/search/' . $this->getInput('u');
|
||||
|
||||
|
@@ -21,6 +21,10 @@ class TebeoBridge extends FeedExpander {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'images/header_logo.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$url = self::URI . '/le-replay/' . $this->getInput('cat');
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
@@ -31,7 +35,7 @@ class TebeoBridge extends FeedExpander {
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
$item['title'] = $element->find('h3', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($element->find('p.moment-format-day', 0)->plaintext);
|
||||
$item['content'] = '<a href="'.$item['uri'].'"><img alt="" src="'.$element->find('img', 0)->src.'"></a>';
|
||||
$item['content'] = '<a href="' . $item['uri'] . '"><img alt="" src="' . $element->find('img', 0)->src . '"></a>';
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ class ThePirateBayBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'The Pirate Bay';
|
||||
const URI = 'https://thepiratebay.org/';
|
||||
const URI = 'https://thepiratebay.wf/';
|
||||
const DESCRIPTION = 'Returns results for the keywords. You can put several
|
||||
list of keywords by separating them with a semicolon (e.g. "one show;another
|
||||
show"). Category based search needs the category number as input. User based
|
||||
|
@@ -158,6 +158,10 @@ class TheTVDBBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'application/themes/thetvdb/images/logo.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$serie_id = $this->getInput('serie_id');
|
||||
$nbepisode = $this->getInput('nb_episode');
|
||||
|
@@ -25,12 +25,12 @@ class TheYeteeBridge extends BridgeAbstract {
|
||||
$item['author'] = $author;
|
||||
|
||||
$uri = $element->find('div[class=controls] a', 0)->href;
|
||||
$item['uri'] = static::URI.$uri;
|
||||
$item['uri'] = static::URI . $uri;
|
||||
|
||||
$content = '<p>'.$element->find('section[class=product-listing-info] p', -1)->plaintext.'</p>';
|
||||
$content = '<p>' . $element->find('section[class=product-listing-info] p', -1)->plaintext . '</p>';
|
||||
$photos = $element->find('a[class=js-modaal-gallery] img');
|
||||
foreach($photos as $photo) {
|
||||
$content = $content."<br /><img src='$photo->src' />";
|
||||
$content = $content . "<br /><img src='$photo->src' />";
|
||||
$item['enclosures'][] = $photo->src;
|
||||
}
|
||||
$item['content'] = $content;
|
||||
|
167
bridges/ThingiverseBridge.php
Normal file
167
bridges/ThingiverseBridge.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
class ThingiverseBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Thingiverse Search';
|
||||
const URI = 'https://thingiverse.com';
|
||||
const DESCRIPTION = 'Returns feeds for search results';
|
||||
const MAINTAINER = 'AntoineTurmel';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'query' => array(
|
||||
'name' => 'Search query',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Insert your search term here',
|
||||
'exampleValue' => 'Enter your search term'
|
||||
),
|
||||
'sortby' => array(
|
||||
'name' => 'Sort by',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'Relevant' => 'relevant',
|
||||
'Text' => 'text',
|
||||
'Popular' => 'popular',
|
||||
'# of Makes' => 'makes',
|
||||
'Newest' => 'newest',
|
||||
),
|
||||
'defaultValue' => 'newest'
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'Any' => '',
|
||||
'3D Printing' => '73',
|
||||
'Art' => '63',
|
||||
'Fashion' => '64',
|
||||
'Gadgets' => '65',
|
||||
'Hobby' => '66',
|
||||
'Household' => '67',
|
||||
'Learning' => '69',
|
||||
'Models' => '70',
|
||||
'Tools' => '71',
|
||||
'Toys & Games' => '72',
|
||||
'2D Art' => '144',
|
||||
'Art Tools' => '75',
|
||||
'Coins & Badges' => '143',
|
||||
'Interactive Art' => '78',
|
||||
'Math Art' => '79',
|
||||
'Scans & Replicas' => '145',
|
||||
'Sculptures' => '80',
|
||||
'Signs & Logos' => '76',
|
||||
'Accessories' => '81',
|
||||
'Bracelets' => '82',
|
||||
'Costume' => '142',
|
||||
'Earrings' => '139',
|
||||
'Glasses' => '83',
|
||||
'Jewelry' => '84',
|
||||
'Keychains' => '130',
|
||||
'Rings' => '85',
|
||||
'Audio' => '141',
|
||||
'Camera' => '86',
|
||||
'Computer' => '87',
|
||||
'Mobile Phone' => '88',
|
||||
'Tablet' => '90',
|
||||
'Video Games' => '91',
|
||||
'Automotive' => '155',
|
||||
'DIY' => '93',
|
||||
'Electronics' => '92',
|
||||
'Music' => '94',
|
||||
'R/C Vehicles' => '95',
|
||||
'Robotics' => '96',
|
||||
'Sport & Outdoors' => '140',
|
||||
'Bathroom' => '147',
|
||||
'Containers' => '146',
|
||||
'Decor' => '97',
|
||||
'Household Supplies' => '99',
|
||||
'Kitchen & Dining' => '100',
|
||||
'Office' => '101',
|
||||
'Organization' => '102',
|
||||
'Outdoor & Garden' => '98',
|
||||
'Pets' => '103',
|
||||
'Replacement Parts' => '153',
|
||||
'Biology' => '106',
|
||||
'Engineering' => '104',
|
||||
'Math' => '105',
|
||||
'Physics & Astronomy' => '148',
|
||||
'Animals' => '107',
|
||||
'Buildings & Structures' => '108',
|
||||
'Creatures' => '109',
|
||||
'Food & Drink' => '110',
|
||||
'Model Furniture' => '111',
|
||||
'Model Robots' => '115',
|
||||
'People' => '112',
|
||||
'Props' => '114',
|
||||
'Vehicles' => '116',
|
||||
'Hand Tools' => '118',
|
||||
'Machine Tools' => '117',
|
||||
'Parts' => '119',
|
||||
'Tool Holders & Boxes' => '120',
|
||||
'Chess' => '151',
|
||||
'Construction Toys' => '121',
|
||||
'Dice' => '122',
|
||||
'Games' => '123',
|
||||
'Mechanical Toys' => '124',
|
||||
'Playsets' => '113',
|
||||
'Puzzles' => '125',
|
||||
'Toy & Game Accessories' => '149',
|
||||
'3D Printer Accessories' => '127',
|
||||
'3D Printer Extruders' => '152',
|
||||
'3D Printer Parts' => '128',
|
||||
'3D Printers' => '126',
|
||||
'3D Printing Tests' => '129',
|
||||
),
|
||||
'defaultValue' => ''
|
||||
),
|
||||
'showimage' => array(
|
||||
'name' => 'Show image in content',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Activate to show the image in the content',
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Failed to receive ' . $this->getURI());
|
||||
|
||||
$results = $html->find('div.thing-card');
|
||||
|
||||
foreach($results as $result) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $result->find('span.ellipsis', 0);
|
||||
$item['uri'] = self::URI . $result->find('a', 1)->href;
|
||||
$item['author'] = $result->find('span.item-creator', 0);
|
||||
$item['content'] = '';
|
||||
|
||||
$image = $result->find('img.card-img', 0)->src;
|
||||
|
||||
if($this->getInput('showimage')) {
|
||||
$item['content'] .= '<img src="' . $image . '">';
|
||||
}
|
||||
|
||||
$item['enclosures'] = array($image);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('query'))) {
|
||||
$uri = self::URI . '/search?q=' . urlencode($this->getInput('query'));
|
||||
$uri .= '&sort=' . $this->getInput('sortby');
|
||||
$uri .= '&category_id=' . $this->getInput('category');
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
}
|
@@ -55,7 +55,7 @@ class UnsplashBridge extends BridgeAbstract {
|
||||
$item['uri'] = str_replace(
|
||||
array('q=75', 'w=400'),
|
||||
array("q=$quality", "w=$width"),
|
||||
$thumbnail->src).'.jpg'; // '.jpg' only for format hint
|
||||
$thumbnail->src) . '.jpg'; // '.jpg' only for format hint
|
||||
|
||||
$item['timestamp'] = time();
|
||||
$item['title'] = $thumbnail->alt;
|
||||
|
@@ -377,7 +377,7 @@ class VkBridge extends BridgeAbstract
|
||||
);
|
||||
$post_videos[] = $video_id;
|
||||
} else {
|
||||
$content_suffix .= '<br>Video: <a href="'.htmlspecialchars($video_link).'">'.$video_title.'</a>';
|
||||
$content_suffix .= '<br>Video: <a href="' . htmlspecialchars($video_link) . '">' . $video_title . '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +390,7 @@ class VkBridge extends BridgeAbstract
|
||||
if (isset($result['error'])) return;
|
||||
|
||||
foreach($result['response']['items'] as $item) {
|
||||
$video_id = strval($item['owner_id']).'_'.strval($item['id']);
|
||||
$video_id = strval($item['owner_id']) . '_' . strval($item['id']);
|
||||
$this->videos[$video_id]['url'] = $item['player'];
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ class VkBridge extends BridgeAbstract
|
||||
foreach($item['videos'] as $video_id) {
|
||||
$video_link = $this->videos[$video_id]['url'];
|
||||
$video_title = $this->videos[$video_id]['title'];
|
||||
$item['content'] .= '<br>Video: <a href="'.htmlspecialchars($video_link).'">'.$video_title.'</a>';
|
||||
$item['content'] .= '<br>Video: <a href="' . htmlspecialchars($video_link) . '">' . $video_title . '</a>';
|
||||
}
|
||||
unset($item['videos']);
|
||||
}
|
||||
@@ -408,6 +408,6 @@ class VkBridge extends BridgeAbstract
|
||||
{
|
||||
$params['v'] = '5.80';
|
||||
$params['access_token'] = $this->getAccessToken();
|
||||
return json_decode( getContents('https://api.vk.com/method/'.$method.'?'.http_build_query($params)), true );
|
||||
return json_decode( getContents('https://api.vk.com/method/' . $method . '?' . http_build_query($params)), true );
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,11 @@ class WhydBridge extends BridgeAbstract {
|
||||
|
||||
private $userName = '';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/favicons/
|
||||
32-6b62a9f14d5e1a9213090d8f00f286bba3a6022381a76390d1d0926493b12593.png?v=6';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
if(strlen(preg_replace('/[^0-9a-f]/', '', $this->getInput('u'))) == 24) {
|
||||
|
@@ -77,7 +77,7 @@ class WordPressPluginUpdateBridge extends BridgeAbstract {
|
||||
debugMessage('getting pubdate from url ' . $url . '');
|
||||
// Initialize cache
|
||||
$cache = Cache::create('FileCache');
|
||||
$cache->setPath(CACHE_DIR . '/pages');
|
||||
$cache->setPath(PATH_CACHE . '/pages');
|
||||
$params = [$url];
|
||||
$cache->setParameters($params);
|
||||
// Get cachefile timestamp
|
||||
|
462
bridges/XenForoBridge.php
Normal file
462
bridges/XenForoBridge.php
Normal file
@@ -0,0 +1,462 @@
|
||||
<?php
|
||||
/**
|
||||
* This bridge generates feeds for threads from forums running XenForo version 2
|
||||
*
|
||||
* Examples:
|
||||
* - https://xenforo.com/community/
|
||||
* - http://www.ign.com/boards/
|
||||
*
|
||||
* Notice: XenForo does provide RSS feeds for forums. For example:
|
||||
* - https://xenforo.com/community/forums/-/index.rss
|
||||
*
|
||||
* For more information on XenForo, visit
|
||||
* - https://xenforo.com/
|
||||
* - https://en.wikipedia.org/wiki/XenForo
|
||||
*/
|
||||
class XenForoBridge extends BridgeAbstract {
|
||||
|
||||
// Bridge specific constants
|
||||
const CONTEXT_THREAD = 'Thread';
|
||||
const XENFORO_VERSION_1 = '1.0';
|
||||
const XENFORO_VERSION_2 = '2.0';
|
||||
|
||||
// RSS-Bridge constants
|
||||
const NAME = 'XenForo Bridge';
|
||||
const URI = 'https://xenforo.com/';
|
||||
const DESCRIPTION = 'Generates feeds for threads in forums powered by XenForo';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const PARAMETERS = array(
|
||||
self::CONTEXT_THREAD => array(
|
||||
'url' => array(
|
||||
'name' => 'Thread URL',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Insert URL to the thread for which the feed should be generated',
|
||||
'exampleValue' => 'https://xenforo.com/community/threads/guide-to-suggestions.2285/'
|
||||
)
|
||||
),
|
||||
'global' => array(
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Specify maximum number of elements to return in the feed',
|
||||
'defaultValue' => 10
|
||||
)
|
||||
)
|
||||
);
|
||||
const CACHE_TIMEOUT = 7200; // 10 minutes
|
||||
|
||||
private $title = '';
|
||||
private $threadurl = '';
|
||||
private $version; // Holds the XenForo version
|
||||
|
||||
public function getName() {
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case self::CONTEXT_THREAD: return $this->title . ' - ' . static::NAME;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case self::CONTEXT_THREAD: return $this->threadurl;
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$this->threadurl = filter_var(
|
||||
$this->getInput('url'),
|
||||
FILTER_VALIDATE_URL,
|
||||
FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED);
|
||||
|
||||
if($this->threadurl === false) {
|
||||
returnClientError('The URL you provided is invalid!');
|
||||
}
|
||||
|
||||
$urlparts = parse_url($this->threadurl, PHP_URL_SCHEME);
|
||||
|
||||
// Scheme must be "http" or "https"
|
||||
if(preg_match('/http[s]{0,1}/', parse_url($this->threadurl, PHP_URL_SCHEME)) == false) {
|
||||
returnClientError('The URL you provided doesn\'t specify a valid scheme (http or https)!');
|
||||
}
|
||||
|
||||
// Path cannot be root (../)
|
||||
if(parse_url($this->threadurl, PHP_URL_PATH) === '/') {
|
||||
returnClientError('The URL you provided doesn\'t link to a valid thread (root path)!');
|
||||
}
|
||||
|
||||
// XenForo adds a thread ID to the URL, like "...-thread.454934283". It must be present
|
||||
if(preg_match('/.+\.\d+[\/]{0,1}/', parse_URL($this->threadurl, PHP_URL_PATH)) == false) {
|
||||
returnClientError('The URL you provided doesn\'t link to a valid thread (ID missing)!');
|
||||
}
|
||||
|
||||
// We want to start at the first page in the thread. XenForo uses "../page-n" syntax
|
||||
// to identify pages (except for the first page).
|
||||
// Notice: XenForo uses the concept of "sentinels" to find and replace parts in the
|
||||
// URL. Technically forum hosts can change the syntax!
|
||||
if(preg_match('/.+\/(page-\d+.*)$/', $this->threadurl, $matches) != false) {
|
||||
|
||||
// before: https://xenforo.com/community/threads/guide-to-suggestions.2285/page-5
|
||||
// after : https://xenforo.com/community/threads/guide-to-suggestions.2285/
|
||||
$this->threadurl = str_replace($matches[1], '', $this->threadurl);
|
||||
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOMCached($this->threadurl)
|
||||
or returnServerError('Failed loading data from "' . $this->threadurl . '"!');
|
||||
|
||||
$html = defaultLinkTo($html, $this->threadurl);
|
||||
|
||||
// Notice: The DOM structure changes depending on the XenForo version used
|
||||
if($mainContent = $html->find('div.mainContent', 0)) {
|
||||
$this->version = self::XENFORO_VERSION_1;
|
||||
} elseif ($mainContent = $html->find('div[class="p-body"]', 0)) {
|
||||
$this->version = self::XENFORO_VERSION_2;
|
||||
} else {
|
||||
returnServerError('This forum is currently not supported!');
|
||||
}
|
||||
|
||||
switch($this->version) {
|
||||
case self::XENFORO_VERSION_1:
|
||||
|
||||
$titleBar = $mainContent->find('div.titleBar h1', 0)
|
||||
or returnServerError('Error finding title bar!');
|
||||
|
||||
$this->title = $titleBar->plaintext;
|
||||
|
||||
// Store items from current page (we'll use $this->items as LIFO buffer)
|
||||
$this->extractThreadPostsV1($html, $this->threadurl);
|
||||
$this->extractPagesV1($html);
|
||||
|
||||
break;
|
||||
|
||||
case self::XENFORO_VERSION_2:
|
||||
|
||||
$titleBar = $mainContent->find('div[class="p-title"] h1', 0)
|
||||
or returnServerError('Error finding title bar!');
|
||||
|
||||
$this->title = $titleBar->plaintext;
|
||||
$this->extractThreadPostsV2($html, $this->threadurl);
|
||||
$this->extractPagesV2($html);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
while(count($this->items) > $this->getInput('limit')) {
|
||||
array_shift($this->items);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts thread posts
|
||||
* @param $html A simplehtmldom object
|
||||
* @param $url The url from which $html was loaded
|
||||
*/
|
||||
private function extractThreadPostsV1($html, $url) {
|
||||
|
||||
$lang = $html->find('html', 0)->lang;
|
||||
|
||||
// Posts are contained in an "ol"
|
||||
$messageList = $html->find('#messageList li')
|
||||
or returnServerError('Error finding message list!');
|
||||
|
||||
foreach($messageList as $post) {
|
||||
|
||||
if(!isset($post->attr['id'])) { // Skip ads
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $url . '#' . $post->getAttribute('id');
|
||||
|
||||
$content = $post->find('.messageContent article', 0);
|
||||
|
||||
// Add some style to quotes
|
||||
foreach($content->find('.bbCodeQuote') as $quote) {
|
||||
$quote->style = '
|
||||
color: #495566;
|
||||
background-color: rgb(248,251,253);
|
||||
border: 1px solid rgb(111, 140, 180);
|
||||
border-color: rgb(111, 140, 180);
|
||||
font-style: italic;';
|
||||
}
|
||||
|
||||
// Remove script tags
|
||||
foreach($content->find('script') as $script) {
|
||||
$script->outertext = '';
|
||||
}
|
||||
|
||||
$item['content'] = $content->innertext;
|
||||
|
||||
// Remove quotes (for the title)
|
||||
foreach($content->find('.bbCodeQuote') as $quote) {
|
||||
$quote->innertext = '';
|
||||
}
|
||||
|
||||
$title = trim($content->plaintext);
|
||||
|
||||
if(strlen($title) > 70) {
|
||||
$item['title'] = substr($title, 0, strpos($title, ' ', 70)) . '...';
|
||||
} else {
|
||||
$item['title'] = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps are presented in two forms:
|
||||
*
|
||||
* 1) short version (for older posts?)
|
||||
* <span
|
||||
* class="DateTime"
|
||||
* title="22 Oct. 2018 at 23:47"
|
||||
* >22 Oct. 2018</span>
|
||||
*
|
||||
* This form has to be interpreted depending on the current language.
|
||||
*
|
||||
* 2) long version (for newer posts?)
|
||||
* <abbr
|
||||
* class="DateTime"
|
||||
* data-time="1541008785"
|
||||
* data-diff="310694"
|
||||
* data-datestring="31 Oct. 2018"
|
||||
* data-timestring="18:59"
|
||||
* title="31 Oct. 2018 at 18:59"
|
||||
* >Wednesday at 18:59</abbr>
|
||||
*
|
||||
* This form has the timestamp embedded (data-time)
|
||||
*/
|
||||
if($timestamp = $post->find('abbr.DateTime', 0)) { // long version (preffered)
|
||||
$item['timestamp'] = $timestamp->{'data-time'};
|
||||
} elseif($timestamp = $post->find('span.DateTime', 0)) { // short version
|
||||
$item['timestamp'] = $this->fixDate($timestamp->title, $lang);
|
||||
}
|
||||
|
||||
$item['author'] = $post->getAttribute('data-author');
|
||||
|
||||
// Bridge specific properties
|
||||
$item['id'] = $post->getAttribute('id');
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function extractThreadPostsV2($html, $url) {
|
||||
|
||||
$lang = $html->find('html', 0)->lang;
|
||||
|
||||
$messageList = $html->find('div[class="block-body"] article')
|
||||
or returnServerError('Error finding message list!');
|
||||
|
||||
foreach($messageList as $post) {
|
||||
|
||||
if(!isset($post->attr['id'])) { // Skip ads
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $url . '#' . $post->getAttribute('id');
|
||||
|
||||
$title = $post->find('div[class="message-content"] article', 0)->plaintext;
|
||||
$end = strpos($title, ' ', 70);
|
||||
$item['title'] = substr($title, 0, $end);
|
||||
|
||||
$item['timestamp'] = $this->fixDate($post->find('time', 0)->title, $lang);
|
||||
$item['author'] = $post->getAttribute('data-author');
|
||||
$item['content'] = $post->find('div[class="message-content"] article', 0);
|
||||
|
||||
// Bridge specific properties
|
||||
$item['id'] = $post->getAttribute('id');
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function extractPagesV1($html) {
|
||||
|
||||
// A navigation bar becomes available if the number of posts grows too
|
||||
// high. When this happens we need to load further pages (from last backwards)
|
||||
if(($pageNav = $html->find('div.PageNav', 0))) {
|
||||
|
||||
$lastpage = $pageNav->{'data-last'};
|
||||
$baseurl = $pageNav->{'data-baseurl'};
|
||||
$sentinel = $pageNav->{'data-sentinel'};
|
||||
|
||||
$hosturl = parse_url($this->threadurl, PHP_URL_SCHEME)
|
||||
. '://'
|
||||
. parse_url($this->threadurl, PHP_URL_HOST)
|
||||
. '/';
|
||||
|
||||
$page = $lastpage;
|
||||
|
||||
// Load at least the last page
|
||||
do {
|
||||
|
||||
$pageurl = $hosturl . str_replace($sentinel, $lastpage, $baseurl);
|
||||
|
||||
// We can optimize performance by caching all but the last page
|
||||
if($page != $lastpage) {
|
||||
$html = getSimpleHTMLDOMCached($pageurl)
|
||||
or returnServerError('Error loading contents from ' . $pageurl . '!');
|
||||
} else {
|
||||
$html = getSimpleHTMLDOM($pageurl)
|
||||
or returnServerError('Error loading contents from ' . $pageurl . '!');
|
||||
}
|
||||
|
||||
$html = defaultLinkTo($html, $hosturl);
|
||||
|
||||
$this->extractThreadPostsV1($html, $pageurl);
|
||||
|
||||
$page--;
|
||||
|
||||
} while (count($this->items) < $this->getInput('limit') && $page != 1);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function extractPagesV2($html) {
|
||||
|
||||
// A navigation bar becomes available if the number of posts grows too
|
||||
// high. When this happens we need to load further pages (from last backwards)
|
||||
if(($pageNav = $html->find('div.pageNav', 0))) {
|
||||
|
||||
foreach($pageNav->find('li') as $nav) {
|
||||
$lastpage = $nav->plaintext;
|
||||
}
|
||||
|
||||
// Manually extract baseurl and inject sentinel
|
||||
$baseurl = $pageNav->find('li a', -1)->href;
|
||||
$baseurl = str_replace('page-' . $lastpage, 'page-{{sentinel}}', $baseurl);
|
||||
|
||||
$sentinel = '{{sentinel}}';
|
||||
|
||||
$hosturl = parse_url($this->threadurl, PHP_URL_SCHEME)
|
||||
. '://'
|
||||
. parse_url($this->threadurl, PHP_URL_HOST);
|
||||
|
||||
$page = $lastpage;
|
||||
|
||||
// Load at least the last page
|
||||
do {
|
||||
|
||||
$pageurl = $hosturl . str_replace($sentinel, $lastpage, $baseurl);
|
||||
|
||||
// We can optimize performance by caching all but the last page
|
||||
if($page != $lastpage) {
|
||||
$html = getSimpleHTMLDOMCached($pageurl)
|
||||
or returnServerError('Error loading contents from ' . $pageurl . '!');
|
||||
} else {
|
||||
$html = getSimpleHTMLDOM($pageurl)
|
||||
or returnServerError('Error loading contents from ' . $pageurl . '!');
|
||||
}
|
||||
|
||||
$html = defaultLinkTo($html, $this->hosturl);
|
||||
|
||||
$this->extractThreadPostsV2($html, $this->pageurl);
|
||||
|
||||
$page--;
|
||||
|
||||
} while (count($this->items) < $this->getInput('limit') && $page != 1);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes dates depending on the choosen language:
|
||||
*
|
||||
* de : dd.mm.yy
|
||||
* en : dd.mm.yy
|
||||
* it : dd/mm/yy
|
||||
*
|
||||
* Basically strtotime doesn't convert dates correctly due to formats
|
||||
* being hard to interpret. So we use the DateTime object.
|
||||
*
|
||||
* We don't know the timezone, so just assume +00:00 (or whatever
|
||||
* DateTime chooses)
|
||||
*/
|
||||
private function fixDate($date, $lang = 'en-US') {
|
||||
|
||||
$mnamesen = [
|
||||
'January',
|
||||
'Feburary',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
];
|
||||
|
||||
switch($lang) {
|
||||
case 'en-US': // example: Jun 9, 2018 at 11:46 PM
|
||||
|
||||
$df = date_create_from_format('M d, Y \a\t H:i A', $date);
|
||||
break;
|
||||
|
||||
case 'de-DE': // example: 19 Juli 2018 um 19:27 Uhr
|
||||
|
||||
$mnamesde = [
|
||||
'Januar',
|
||||
'Februar',
|
||||
'März',
|
||||
'April',
|
||||
'Mai',
|
||||
'Juni',
|
||||
'Juli',
|
||||
'August',
|
||||
'September',
|
||||
'Oktober',
|
||||
'November',
|
||||
'Dezember'
|
||||
];
|
||||
|
||||
$mnamesdeshort = [
|
||||
'Jan.',
|
||||
'Feb.',
|
||||
'Mär.',
|
||||
'Apr.',
|
||||
'Mai',
|
||||
'Juni',
|
||||
'Juli',
|
||||
'Aug.',
|
||||
'Sep.',
|
||||
'Okt.',
|
||||
'Nov.',
|
||||
'Dez.'
|
||||
];
|
||||
|
||||
$date = str_ireplace($mnamesde, $mnamesen, $date);
|
||||
$date = str_ireplace($mnamesdeshort, $mnamesen, $date);
|
||||
|
||||
$df = date_create_from_format('d M Y \u\m H:i \U\h\r', $date);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// debugMessage(date_format($df, 'U'));
|
||||
|
||||
return date_format($df, 'U');
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -147,12 +147,19 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
$time = 0;
|
||||
$vid = str_replace('/watch?v=', '', $element->find('a', 0)->href);
|
||||
$vid = substr($vid, 0, strpos($vid, '&') ?: strlen($vid));
|
||||
$title = $this->ytBridgeFixTitle($element->find($title_selector, 0)->plaintext);
|
||||
$title = trim($this->ytBridgeFixTitle($element->find($title_selector, 0)->plaintext));
|
||||
|
||||
if (strpos($vid, 'googleads') !== false
|
||||
|| $title == '[Private video]'
|
||||
|| $title == '[Deleted video]'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The duration comes in one of the formats:
|
||||
// hh:mm:ss / mm:ss / m:ss
|
||||
// 01:03:30 / 15:06 / 1:24
|
||||
$durationText = trim($element->find('span[class="video-time"]', 0)->plaintext);
|
||||
$durationText = trim($element->find('div.timestamp span', 0)->plaintext);
|
||||
$durationText = preg_replace('/([\d]{1,2})\:([\d]{2})/', '00:$1:$2', $durationText);
|
||||
|
||||
sscanf($durationText, '%d:%d:%d', $hours, $minutes, $seconds);
|
||||
@@ -162,13 +169,11 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
continue;
|
||||
}
|
||||
|
||||
if($title != '[Private Video]' && strpos($vid, 'googleads') === false) {
|
||||
if ($add_parsed_items) {
|
||||
$this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time);
|
||||
$this->ytBridgeAddItem($vid, $title, $author, $desc, $time);
|
||||
}
|
||||
$count++;
|
||||
if ($add_parsed_items) {
|
||||
$this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time);
|
||||
$this->ytBridgeAddItem($vid, $title, $author, $desc, $time);
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
@@ -181,7 +186,9 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
|
||||
private function ytGetSimpleHTMLDOM($url){
|
||||
return getSimpleHTMLDOM($url,
|
||||
$header = array(),
|
||||
$header = array(
|
||||
'Accept-Language: en-US'
|
||||
),
|
||||
$opts = array(),
|
||||
$lowercase = true,
|
||||
$forceTagsClosed = true,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class ZoneTelechargementBridge extends BridgeAbstract {
|
||||
const NAME = 'Zone Telechargement';
|
||||
const URI = 'https://ww4.zone-telechargement1.org/';
|
||||
const URI = 'https://www.zone-telechargement1.org/';
|
||||
const DESCRIPTION = 'Suivi de série sur Zone Telechargement';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
const PARAMETERS = array(
|
||||
@@ -12,9 +12,13 @@ class ZoneTelechargementBridge extends BridgeAbstract {
|
||||
'required' => true,
|
||||
'title' => 'URL d\'une série sans le https://ww4.zone-telechargement1.org/',
|
||||
'exampleValue' => 'telecharger-series/31079-halt-and-catch-fire-saison-4-french-hd720p.html'
|
||||
)
|
||||
)
|
||||
);
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://ww7.zone-telechargement1.org/templates/Default/images/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
|
||||
@@ -39,7 +43,7 @@ class ZoneTelechargementBridge extends BridgeAbstract {
|
||||
$hoster = $this->findLinkHoster($element);
|
||||
|
||||
// Format the link and add the link to the corresponding episode table
|
||||
$episodes[$epnumber][] = '<a href="' . $element->href . '">'. $hoster . ' - '
|
||||
$episodes[$epnumber][] = '<a href="' . $element->href . '">' . $hoster . ' - '
|
||||
. $this->showTitle . ' Episode ' . $epnumber . '</a>';
|
||||
|
||||
}
|
||||
@@ -58,7 +62,7 @@ class ZoneTelechargementBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Suivre la publication des épisodes d\'une série en cours de diffusion':
|
||||
return $this->showTitle . ' - ' . self::NAME;
|
||||
@@ -68,8 +72,7 @@ class ZoneTelechargementBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
private function findLinkHoster($element)
|
||||
{
|
||||
private function findLinkHoster($element) {
|
||||
// The hoster name is one level higher than the link tag : get the parent element
|
||||
$element = $element->parent();
|
||||
//echo "PARENT : $element \n";
|
||||
|
@@ -11,6 +11,12 @@
|
||||
; false = disabled (default)
|
||||
custom_timeout = false
|
||||
|
||||
[admin]
|
||||
; Advertise an email address where people can reach the administrator.
|
||||
; This address is displayed on the main page, visible to everyone!
|
||||
; "" = Disabled (default)
|
||||
email = ""
|
||||
|
||||
[proxy]
|
||||
|
||||
; Sets the proxy url (i.e. "tcp://192.168.0.0:32")
|
||||
|
@@ -15,13 +15,13 @@ class AtomFormat extends FormatAbstract{
|
||||
|
||||
$extraInfos = $this->getExtraInfos();
|
||||
$title = $this->xml_encode($extraInfos['name']);
|
||||
$uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : 'https://github.com/RSS-Bridge/rss-bridge';
|
||||
$uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : REPOSITORY;
|
||||
|
||||
$uriparts = parse_url($uri);
|
||||
if(!empty($extraInfos['icon'])) {
|
||||
$icon = $extraInfos['icon'];
|
||||
} else {
|
||||
$icon = $this->xml_encode($uriparts['scheme'] . '://' . $uriparts['host'] .'/favicon.ico');
|
||||
$icon = $this->xml_encode($uriparts['scheme'] . '://' . $uriparts['host'] . '/favicon.ico');
|
||||
}
|
||||
|
||||
$uri = $this->xml_encode($uri);
|
||||
|
@@ -18,11 +18,11 @@ class MrssFormat extends FormatAbstract {
|
||||
if(!empty($extraInfos['uri'])) {
|
||||
$uri = $this->xml_encode($extraInfos['uri']);
|
||||
} else {
|
||||
$uri = 'https://github.com/RSS-Bridge/rss-bridge';
|
||||
$uri = REPOSITORY;
|
||||
}
|
||||
|
||||
$uriparts = parse_url($uri);
|
||||
$icon = $this->xml_encode($uriparts['scheme'] . '://' . $uriparts['host'] .'/favicon.ico');
|
||||
$icon = $this->xml_encode($uriparts['scheme'] . '://' . $uriparts['host'] . '/favicon.ico');
|
||||
|
||||
$items = '';
|
||||
foreach($this->getItems() as $item) {
|
||||
@@ -56,7 +56,7 @@ Some media files might not be shown to you. Consider using the ATOM format inste
|
||||
|
||||
foreach($item['categories'] as $category) {
|
||||
$entryCategories .= '<category>'
|
||||
. $category . '</category>'
|
||||
. $category . '</category>'
|
||||
. PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
50
index.php
50
index.php
@@ -28,11 +28,6 @@ if(file_exists('DEBUG')) {
|
||||
|
||||
require_once __DIR__ . '/lib/RssBridge.php';
|
||||
|
||||
define('PHP_VERSION_REQUIRED', '5.6.0');
|
||||
|
||||
// Specify directory for cached files (using FileCache)
|
||||
define('CACHE_DIR', __DIR__ . '/cache');
|
||||
|
||||
// Specify path for whitelist file
|
||||
define('WHITELIST_FILE', __DIR__ . '/whitelist.txt');
|
||||
|
||||
@@ -54,13 +49,15 @@ if (isset($argv)) {
|
||||
$params = $_GET;
|
||||
}
|
||||
|
||||
// FIXME : beta test UA spoofing, please report any blacklisting by PHP-fopen-unfriendly websites
|
||||
define('USER_AGENT',
|
||||
'Mozilla/5.0 (X11; Linux x86_64; rv:30.0) Gecko/20121202 Firefox/30.0(rss-bridge/'
|
||||
. Configuration::$VERSION
|
||||
. ';+'
|
||||
. REPOSITORY
|
||||
. ')'
|
||||
);
|
||||
|
||||
$userAgent = 'Mozilla/5.0(X11; Linux x86_64; rv:30.0)';
|
||||
$userAgent .= ' Gecko/20121202 Firefox/30.0(rss-bridge/0.1;';
|
||||
$userAgent .= '+https://github.com/RSS-Bridge/rss-bridge)';
|
||||
|
||||
ini_set('user_agent', $userAgent);
|
||||
ini_set('user_agent', USER_AGENT);
|
||||
|
||||
// default whitelist
|
||||
$whitelist_default = array(
|
||||
@@ -83,9 +80,9 @@ $whitelist_default = array(
|
||||
|
||||
try {
|
||||
|
||||
Bridge::setDir(__DIR__ . '/bridges/');
|
||||
Format::setDir(__DIR__ . '/formats/');
|
||||
Cache::setDir(__DIR__ . '/caches/');
|
||||
Bridge::setDir(PATH_LIB_BRIDGES);
|
||||
Format::setDir(PATH_LIB_FORMATS);
|
||||
Cache::setDir(PATH_LIB_CACHES);
|
||||
|
||||
if(!file_exists(WHITELIST_FILE)) {
|
||||
$whitelist_selection = $whitelist_default;
|
||||
@@ -202,6 +199,7 @@ try {
|
||||
'format',
|
||||
'_noproxy',
|
||||
'_cache_timeout',
|
||||
'_error_time'
|
||||
), '')
|
||||
);
|
||||
|
||||
@@ -214,12 +212,13 @@ try {
|
||||
'format',
|
||||
'_noproxy',
|
||||
'_cache_timeout',
|
||||
'_error_time'
|
||||
), '')
|
||||
);
|
||||
|
||||
// Initialize cache
|
||||
$cache = Cache::create('FileCache');
|
||||
$cache->setPath(CACHE_DIR);
|
||||
$cache->setPath(PATH_CACHE);
|
||||
$cache->purgeCache(86400); // 24 hours
|
||||
$cache->setParameters($cache_params);
|
||||
|
||||
@@ -237,7 +236,7 @@ try {
|
||||
$stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
|
||||
|
||||
if($mtime <= $stime) { // Cached data is older or same
|
||||
header('HTTP/1.1 304 Not Modified');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304);
|
||||
die();
|
||||
}
|
||||
}
|
||||
@@ -262,6 +261,8 @@ try {
|
||||
'icon' => $bridge->getIcon()
|
||||
);
|
||||
} catch(Error $e) {
|
||||
error_log($e);
|
||||
|
||||
$item = array();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
@@ -280,6 +281,8 @@ try {
|
||||
|
||||
$items[] = $item;
|
||||
} catch(Exception $e) {
|
||||
error_log($e);
|
||||
|
||||
$item = array();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
@@ -306,24 +309,25 @@ try {
|
||||
$format = Format::create($format);
|
||||
$format->setItems($items);
|
||||
$format->setExtraInfos($infos);
|
||||
$format->setLastModified($mtime);
|
||||
$format->setLastModified($cache->getTime());
|
||||
$format->display();
|
||||
} catch(Error $e) {
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/html');
|
||||
error_log($e);
|
||||
header('Content-Type: text/html', true, $e->getCode());
|
||||
die(buildTransformException($e, $bridge));
|
||||
} catch(Exception $e) {
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/html');
|
||||
error_log($e);
|
||||
header('Content-Type: text/html', true, $e->getCode());
|
||||
die(buildTransformException($e, $bridge));
|
||||
}
|
||||
} else {
|
||||
echo BridgeList::create($whitelist_selection, $showInactive);
|
||||
}
|
||||
} catch(HttpException $e) {
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/plain');
|
||||
error_log($e);
|
||||
header('Content-Type: text/plain', true, $e->getCode());
|
||||
die($e->getMessage());
|
||||
} catch(\Exception $e) {
|
||||
error_log($e);
|
||||
die($e->getMessage());
|
||||
}
|
||||
|
@@ -5,8 +5,7 @@ class Authentication {
|
||||
|
||||
if(Configuration::getConfig('authentication', 'enable') === true) {
|
||||
if(!Authentication::verifyPrompt()) {
|
||||
header('WWW-Authenticate: Basic realm="RSS-Bridge"');
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
header('WWW-Authenticate: Basic realm="RSS-Bridge"', true, 401);
|
||||
die('Please authenticate in order to access this instance !');
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/BridgeInterface.php');
|
||||
|
||||
class Bridge {
|
||||
|
||||
static protected $dirBridge;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/BridgeInterface.php');
|
||||
|
||||
abstract class BridgeAbstract implements BridgeInterface {
|
||||
|
||||
const NAME = 'Unnamed bridge';
|
||||
|
@@ -193,7 +193,7 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
. '" type="checkbox" name="'
|
||||
. $name
|
||||
. '" '
|
||||
. ($entry['defaultValue'] === 'checked' ?: '')
|
||||
. ($entry['defaultValue'] === 'checked' ? 'checked' : '')
|
||||
. ' />'
|
||||
. PHP_EOL;
|
||||
}
|
||||
|
@@ -96,6 +96,18 @@ EOD;
|
||||
private static function getFooter($totalBridges, $totalActiveBridges, $showInactive) {
|
||||
$version = Configuration::getVersion();
|
||||
|
||||
$email = Configuration::getConfig('admin', 'email');
|
||||
$admininfo = '';
|
||||
if (!empty($email)) {
|
||||
$admininfo = <<<EOD
|
||||
<br />
|
||||
<span>
|
||||
You may email the administrator of this RSS-Bridge instance
|
||||
at <a href="mailto:{$email}">{$email}</a>
|
||||
</span>
|
||||
EOD;
|
||||
}
|
||||
|
||||
$inactive = '';
|
||||
|
||||
if($totalActiveBridges !== $totalBridges) {
|
||||
@@ -114,6 +126,7 @@ EOD;
|
||||
<p class="version">{$version}</p>
|
||||
{$totalActiveBridges}/{$totalBridges} active bridges.<br>
|
||||
{$inactive}
|
||||
{$admininfo}
|
||||
</section>
|
||||
EOD;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/CacheInterface.php');
|
||||
|
||||
class Cache {
|
||||
|
||||
static protected $dirCache;
|
||||
|
@@ -1,15 +1,15 @@
|
||||
<?php
|
||||
class Configuration {
|
||||
|
||||
public static $VERSION = '2018-10-15';
|
||||
public static $VERSION = '2018-11-10';
|
||||
|
||||
public static $config = null;
|
||||
|
||||
public static function verifyInstallation() {
|
||||
|
||||
// Check PHP version
|
||||
if(version_compare(PHP_VERSION, PHP_VERSION_REQUIRED) === -1)
|
||||
die('RSS-Bridge requires at least PHP version ' . PHP_VERSION_REQUIRED . '!');
|
||||
if(version_compare(PHP_VERSION, '5.6.0') === -1)
|
||||
die('RSS-Bridge requires at least PHP version 5.6.0!');
|
||||
|
||||
// extensions check
|
||||
if(!extension_loaded('openssl'))
|
||||
@@ -31,8 +31,8 @@ class Configuration {
|
||||
die('"json" extension not loaded. Please check "php.ini"');
|
||||
|
||||
// Check cache folder permissions (write permissions required)
|
||||
if(!is_writable(CACHE_DIR))
|
||||
die('RSS-Bridge does not have write permissions for ' . CACHE_DIR . '!');
|
||||
if(!is_writable(PATH_CACHE))
|
||||
die('RSS-Bridge does not have write permissions for ' . PATH_CACHE . '!');
|
||||
|
||||
// Check whitelist file permissions (only in DEBUG mode)
|
||||
if(!file_exists(WHITELIST_FILE) && !is_writable(dirname(WHITELIST_FILE)))
|
||||
@@ -107,7 +107,8 @@ class Configuration {
|
||||
|
||||
$headFile = '.git/HEAD';
|
||||
|
||||
if(file_exists($headFile)) {
|
||||
// '@' is used to mute open_basedir warning
|
||||
if(@is_readable($headFile)) {
|
||||
|
||||
$revisionHashFile = '.git/' . substr(file_get_contents($headFile), 5, -1);
|
||||
$branchName = explode('/', $revisionHashFile)[3];
|
||||
|
@@ -19,7 +19,8 @@ function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null
|
||||
}
|
||||
|
||||
// Add title and body
|
||||
$uri = 'https://github.com/rss-bridge/rss-bridge/issues/new?title='
|
||||
$uri = REPOSITORY
|
||||
. 'issues/new?title='
|
||||
. urlencode($title)
|
||||
. '&body='
|
||||
. urlencode($body);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/BridgeInterface.php');
|
||||
|
||||
abstract class FeedExpander extends BridgeAbstract {
|
||||
|
||||
private $name;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/FormatInterface.php');
|
||||
|
||||
class Format {
|
||||
|
||||
static protected $dirFormat;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/FormatInterface.php');
|
||||
|
||||
abstract class FormatAbstract implements FormatInterface {
|
||||
const DEFAULT_CHARSET = 'UTF-8';
|
||||
|
||||
|
@@ -1,69 +1,37 @@
|
||||
<?php
|
||||
/* rss-bridge library.
|
||||
Foundation functions for rss-bridge project.
|
||||
See https://github.com/sebsauvage/rss-bridge
|
||||
Licence: Public domain.
|
||||
*/
|
||||
|
||||
define('PATH_VENDOR', '/../vendor');
|
||||
define('PATH_LIB', __DIR__ . '/../lib/'); // Path to core library
|
||||
define('PATH_LIB_VENDOR', __DIR__ . '/../vendor/'); // Path to vendor library
|
||||
define('PATH_LIB_BRIDGES', __DIR__ . '/../bridges/'); // Path to bridges library
|
||||
define('PATH_LIB_FORMATS', __DIR__ . '/../formats/'); // Path to formats library
|
||||
define('PATH_LIB_CACHES', __DIR__ . '/../caches/'); // Path to caches library
|
||||
define('PATH_CACHE', __DIR__ . '/../cache'); // Path to cache folder
|
||||
define('REPOSITORY', 'https://github.com/RSS-Bridge/rss-bridge/');
|
||||
|
||||
require __DIR__ . '/Exceptions.php';
|
||||
require __DIR__ . '/Format.php';
|
||||
require __DIR__ . '/FormatAbstract.php';
|
||||
require __DIR__ . '/Bridge.php';
|
||||
require __DIR__ . '/BridgeAbstract.php';
|
||||
require __DIR__ . '/FeedExpander.php';
|
||||
require __DIR__ . '/Cache.php';
|
||||
require __DIR__ . '/Authentication.php';
|
||||
require __DIR__ . '/Configuration.php';
|
||||
require __DIR__ . '/BridgeCard.php';
|
||||
require __DIR__ . '/BridgeList.php';
|
||||
require __DIR__ . '/ParameterValidator.php';
|
||||
// Interfaces
|
||||
require_once PATH_LIB . 'BridgeInterface.php';
|
||||
require_once PATH_LIB . 'CacheInterface.php';
|
||||
require_once PATH_LIB . 'FormatInterface.php';
|
||||
|
||||
require __DIR__ . '/html.php';
|
||||
require __DIR__ . '/error.php';
|
||||
require __DIR__ . '/contents.php';
|
||||
// Classes
|
||||
require_once PATH_LIB . 'Exceptions.php';
|
||||
require_once PATH_LIB . 'Format.php';
|
||||
require_once PATH_LIB . 'FormatAbstract.php';
|
||||
require_once PATH_LIB . 'Bridge.php';
|
||||
require_once PATH_LIB . 'BridgeAbstract.php';
|
||||
require_once PATH_LIB . 'FeedExpander.php';
|
||||
require_once PATH_LIB . 'Cache.php';
|
||||
require_once PATH_LIB . 'Authentication.php';
|
||||
require_once PATH_LIB . 'Configuration.php';
|
||||
require_once PATH_LIB . 'BridgeCard.php';
|
||||
require_once PATH_LIB . 'BridgeList.php';
|
||||
require_once PATH_LIB . 'ParameterValidator.php';
|
||||
|
||||
$vendorLibSimpleHtmlDom = __DIR__ . PATH_VENDOR . '/simplehtmldom/simple_html_dom.php';
|
||||
if(!file_exists($vendorLibSimpleHtmlDom)) {
|
||||
throw new \HttpException('"PHP Simple HTML DOM Parser" library is missing.
|
||||
Get it from http://simplehtmldom.sourceforge.net and place the script "simple_html_dom.php" in '
|
||||
. substr(PATH_VENDOR, 4)
|
||||
. '/simplehtmldom/',
|
||||
500);
|
||||
}
|
||||
require_once $vendorLibSimpleHtmlDom;
|
||||
// Functions
|
||||
require_once PATH_LIB . 'html.php';
|
||||
require_once PATH_LIB . 'error.php';
|
||||
require_once PATH_LIB . 'contents.php';
|
||||
|
||||
$vendorLibPhpUrlJoin = __DIR__ . PATH_VENDOR . '/php-urljoin/src/urljoin.php';
|
||||
if(!file_exists($vendorLibPhpUrlJoin)) {
|
||||
throw new \HttpException('"php-urljoin" library is missing.
|
||||
Get it from https://github.com/fluffy-critter/php-urljoin and place the script "urljoin.php" in '
|
||||
. substr(PATH_VENDOR, 4)
|
||||
. '/php-urljoin/src/',
|
||||
500);
|
||||
}
|
||||
require_once $vendorLibPhpUrlJoin;
|
||||
|
||||
|
||||
/* Example use
|
||||
|
||||
require_once __DIR__ . '/lib/RssBridge.php';
|
||||
|
||||
// Data retrieval
|
||||
Bridge::setDir(__DIR__ . '/bridges/');
|
||||
$bridge = Bridge::create('GoogleSearch');
|
||||
$bridge->collectData($_REQUEST);
|
||||
|
||||
// Data transformation
|
||||
Format::setDir(__DIR__ . '/formats/');
|
||||
$format = Format::create('Atom');
|
||||
$format
|
||||
->setItems($bridge->getItems())
|
||||
->setExtraInfos(array(
|
||||
'name' => $bridge->getName(),
|
||||
'uri' => $bridge->getURI(),
|
||||
'icon' => $bridge->getIcon(),
|
||||
))
|
||||
->display();
|
||||
|
||||
*/
|
||||
// Vendor
|
||||
require_once PATH_LIB_VENDOR . 'simplehtmldom/simple_html_dom.php';
|
||||
require_once PATH_LIB_VENDOR . 'php-urljoin/src/urljoin.php';
|
||||
|
@@ -1,24 +1,37 @@
|
||||
<?php
|
||||
function getContents($url, $header = array(), $opts = array()){
|
||||
debugMessage('Reading contents from "' . $url . '"');
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
if(is_array($header) && count($header) !== 0)
|
||||
if(is_array($header) && count($header) !== 0) {
|
||||
|
||||
debugMessage('Setting headers: ' . json_encode($header));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
|
||||
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, ini_get('user_agent'));
|
||||
curl_setopt($ch, CURLOPT_ENCODING, '');
|
||||
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
||||
|
||||
if(is_array($opts)) {
|
||||
if(is_array($opts) && count($opts) !== 0) {
|
||||
|
||||
debugMessage('Setting options: ' . json_encode($opts));
|
||||
|
||||
foreach($opts as $key => $value) {
|
||||
curl_setopt($ch, $key, $value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(defined('PROXY_URL') && !defined('NOPROXY')) {
|
||||
|
||||
debugMessage('Setting proxy url: ' . PROXY_URL);
|
||||
curl_setopt($ch, CURLOPT_PROXY, PROXY_URL);
|
||||
|
||||
}
|
||||
|
||||
// We always want the response header as part of the data!
|
||||
@@ -34,6 +47,9 @@ function getContents($url, $header = array(), $opts = array()){
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$errorCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$header = substr($data, 0, $headerSize);
|
||||
|
||||
debugMessage('Response header: ' . $header);
|
||||
|
||||
$headers = parseResponseHeader($header);
|
||||
$finalHeader = end($headers);
|
||||
|
||||
@@ -48,7 +64,7 @@ EOD
|
||||
}
|
||||
|
||||
returnError(<<<EOD
|
||||
The requested resouce cannot be found!
|
||||
The requested resource cannot be found!
|
||||
Please make sure your input parameters are correct!
|
||||
EOD
|
||||
, $errorCode);
|
||||
@@ -97,7 +113,7 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
|
||||
|
||||
// Initialize cache
|
||||
$cache = Cache::create('FileCache');
|
||||
$cache->setPath(CACHE_DIR . '/pages');
|
||||
$cache->setPath(PATH_CACHE . '/pages');
|
||||
$cache->purgeCache(86400); // 24 hours (forced)
|
||||
|
||||
$params = [$url];
|
||||
|
@@ -155,8 +155,8 @@ function markdownToHtml($string) {
|
||||
$string = preg_replace('/_(.*)_/U', '<i>$1</i>', $string);
|
||||
$string = preg_replace('/[-]{6,99}/', '<hr />', $string);
|
||||
$string = str_replace(' ', '<br />', $string);
|
||||
$string = preg_replace('/([^"])(https?:\/\/[^ "<]+)([^"])/', '$1<a href="$2">$2</a>$3', $string.' ');
|
||||
$string = preg_replace('/([^"\/])(www\.[^ "<]+)([^"])/', '$1<a href="http://$2">$2</a>$3', $string.' ');
|
||||
$string = preg_replace('/([^"])(https?:\/\/[^ "<]+)([^"])/', '$1<a href="$2">$2</a>$3', $string . ' ');
|
||||
$string = preg_replace('/([^"\/])(www\.[^ "<]+)([^"])/', '$1<a href="http://$2">$2</a>$3', $string . ' ');
|
||||
|
||||
//As the regex are not perfect, we need to fix <i> and </i> that are introduced in URLs
|
||||
// Fixup regex <i>: https://regex101.com/r/NTRPf6/1
|
||||
|
47
phpcompatibility.xml
Normal file
47
phpcompatibility.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ruleset name="RSS-Bridge PHPCompatibility">
|
||||
<description>Defines rules for PHPCompatibility</description>
|
||||
<exclude-pattern>./static</exclude-pattern>
|
||||
<exclude-pattern>./vendor</exclude-pattern>
|
||||
|
||||
<!-- Run against the PHPCompatibility ruleset -->
|
||||
<!--
|
||||
|
||||
-->
|
||||
<config name="testVersion" value="5.6"/>
|
||||
<rule ref="PHPCompatibility">
|
||||
<!--
|
||||
"PHP 7 changes how most errors are reported by PHP. Instead of reporting
|
||||
errors through the traditional error reporting mechanism used by PHP 5,
|
||||
most errors are now reported by throwing Error exceptions."
|
||||
|
||||
from: http://php.net/manual/en/language.errors.php7.php
|
||||
|
||||
Skip this check for PHP 5.6 in order to support PHP 7.x
|
||||
|
||||
Catch Exception and Error separately to support both versions.
|
||||
|
||||
Example:
|
||||
|
||||
<code>
|
||||
try {
|
||||
// Run your code here
|
||||
} catch(Error $e) {
|
||||
// Handle errors (PHP 7.0+)
|
||||
} catch(Exception $e) {
|
||||
// Handle exceptions (PHP 5.6+)
|
||||
}
|
||||
</code>
|
||||
-->
|
||||
<exclude name="PHPCompatibility.Classes.NewClasses.errorFound"/>
|
||||
<!--
|
||||
RSS-Bridge uses parse_ini_file with INI_SCANNER_TYPED to load configuration
|
||||
settings. INI_SCANNER_TYPED was added in PHP 5.6.1. Skip checking for that
|
||||
specific constant.
|
||||
|
||||
References: http://php.net/manual/de/function.parse-ini-file.php
|
||||
-->
|
||||
<exclude name="PHPCompatibility.Constants.NewConstants.ini_scanner_typedFound"/>
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
@@ -13,6 +13,13 @@
|
||||
<rule ref="Generic.CodeAnalysis.UnnecessaryFinalModifier"/>
|
||||
<!-- Do not override methods to call their parent -->
|
||||
<rule ref="Generic.CodeAnalysis.UselessOverridingMethod"/>
|
||||
<!-- Make sure the concatenation operator has spaces around it -->
|
||||
<rule ref="Squiz.Strings.ConcatenationSpacing">
|
||||
<properties>
|
||||
<property name="spacing" value="1"/>
|
||||
<property name="ignoreNewlines" value="true"/>
|
||||
</properties>
|
||||
</rule>
|
||||
<!-- One line should not have more than 80 characters -->
|
||||
<!-- One line must never exceed 120 characters -->
|
||||
<rule ref="Generic.Files.LineLength">
|
||||
|
@@ -6,7 +6,7 @@ use PHPUnit\Framework\AssertionFailedError;
|
||||
|
||||
require_once(__DIR__ . '/../lib/RssBridge.php');
|
||||
|
||||
Bridge::setDir(__DIR__ . '/../bridges/');
|
||||
Bridge::setDir(PATH_LIB_BRIDGES);
|
||||
|
||||
/**
|
||||
* This class checks bridges for implementation details:
|
||||
|
9
vendor/php-urljoin/src/urljoin.php
vendored
9
vendor/php-urljoin/src/urljoin.php
vendored
@@ -26,6 +26,15 @@ function urljoin($base, $rel) {
|
||||
$pbase = parse_url($base);
|
||||
$prel = parse_url($rel);
|
||||
|
||||
if ($prel === false || preg_match('/^[a-z0-9\-.]*[^a-z0-9\-.:][a-z0-9\-.]*:/i', $rel)) {
|
||||
/*
|
||||
Either parse_url couldn't parse this, or the original URL
|
||||
fragment had an invalid scheme character before the first :,
|
||||
which can confuse parse_url
|
||||
*/
|
||||
$prel = array('path' => $rel);
|
||||
}
|
||||
|
||||
if (array_key_exists('path', $pbase) && $pbase['path'] === '/') {
|
||||
unset($pbase['path']);
|
||||
}
|
||||
|
Reference in New Issue
Block a user