mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-31 11:52:12 +02:00
Compare commits
166 Commits
2018-10-15
...
2018-12-11
Author | SHA1 | Date | |
---|---|---|---|
|
a11ade3442 | ||
|
3932e7b8ef | ||
|
5305c405f6 | ||
|
1c58c04271 | ||
|
89218f1da6 | ||
|
30e2b79c38 | ||
|
2184f523cd | ||
|
242b6953ed | ||
|
bdcb7a9829 | ||
|
f4b46e497e | ||
|
d5085a4116 | ||
|
d7cabfca54 | ||
|
de575982a1 | ||
|
3d301fc4ee | ||
|
263e8872ea | ||
|
6e9c188a72 | ||
|
49da67cb33 | ||
|
b4dbd191d0 | ||
|
e09f452426 | ||
|
7b261d1cc2 | ||
|
96a518c9e7 | ||
|
0d2ea9a677 | ||
|
66e82e46db | ||
|
54800fcc8d | ||
|
67004556e6 | ||
|
c6a7b9ac64 | ||
|
dbffbd4d4e | ||
|
1c17ffb5c4 | ||
|
326cfb21cf | ||
|
8ab1fb86a9 | ||
|
a9ec3d0d1f | ||
|
ac5bcb62ec | ||
|
f24ab8b51b | ||
|
4348119adf | ||
|
fd4124cda2 | ||
|
91f7405297 | ||
|
85685b7758 | ||
|
41d02554f3 | ||
|
c4550be812 | ||
|
b29ba5b973 | ||
|
254fe9212a | ||
|
3806895059 | ||
|
599d438a0d | ||
|
e5a6baab96 | ||
|
b47a30ecc1 | ||
|
860b36c1e3 | ||
|
3d475572c6 | ||
|
59f2d755fe | ||
|
d7c374bd8c | ||
|
6b6ab6486a | ||
|
6c4e239f64 | ||
|
88b0656954 | ||
|
66b11b8c41 | ||
|
1b34d9860e | ||
|
6e70d461e1 | ||
|
0a92b5d29b | ||
|
e3849f45ab | ||
|
3d9c4a3718 | ||
|
5f146a257e | ||
|
936688e08c | ||
|
4b5372638c | ||
|
6f4a8f4d03 | ||
|
39652bb050 | ||
|
fcac5b8b92 | ||
|
6f7b56cba8 | ||
|
86ac0a4866 | ||
|
4a99c6e630 | ||
|
e8442a3bf8 | ||
|
427688fd67 | ||
|
4a6b3654eb | ||
|
5f867c00b4 | ||
|
c15b25a07d | ||
|
c296e73c18 | ||
|
007ee4d858 | ||
|
dd95ec6200 | ||
|
d951000c23 | ||
|
51634a72e0 | ||
|
78c69b08f0 | ||
|
3bb3353897 | ||
|
e26d61ec0a | ||
|
a0490e3673 | ||
|
c63af2e7ad | ||
|
9379854f7a | ||
|
ecdac1b089 | ||
|
2104fc4d58 | ||
|
697d63bb96 | ||
|
2bb13169b4 | ||
|
4713fb6190 | ||
|
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
|
14
.gitattributes
vendored
14
.gitattributes
vendored
@@ -20,3 +20,17 @@
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
# Ignore files in git archive (i.e. GitHub release builds)
|
||||
Dockerfile export-ignore
|
||||
.travis.yml export-ignore
|
||||
.github/ export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.dockerignore export-ignore
|
||||
scalingo.json export-ignore
|
||||
phpunit.xml export-ignore
|
||||
phpcs.xml export-ignore
|
||||
phpcompatibility.xml export-ignore
|
||||
tests/ export-ignore
|
||||
cache/.gitkeep export-ignore
|
49
.github/CONTRIBUTING.md
vendored
Normal file
49
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
### 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)
|
||||
* [abstract and final declarations MUST precede the visibility declaration](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#abstract-and-final-declarations-must-precede-the-visibility-declaration)
|
||||
* [static declaration MUST come after the visibility declaration](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#static-declaration-must-come-after-the-visibility-declaration)
|
||||
* [Casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting)
|
||||
* [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.
|
||||
-->
|
32
.travis.yml
32
.travis.yml
@@ -1,28 +1,36 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: php
|
||||
|
||||
install:
|
||||
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
|
||||
composer global require squizlabs/PHP_CodeSniffer;
|
||||
else
|
||||
pear channel-update pear.php.net;
|
||||
pear install PHP_CodeSniffer;
|
||||
fi
|
||||
- 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
|
||||
- ~/.config/composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
||||
# Check PHP compatibility for the lowest supported version
|
||||
- if [[ $TRAVIS_PHP_VERSION == "5.6" ]]; then
|
||||
~/.config/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/;
|
||||
~/.config/composer/vendor/bin/phpcs . --standard=PHPCompatibility --warning-severity=0 --extensions=php -p --runtime-set testVersion 5.6-;
|
||||
fi
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
@@ -30,9 +38,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);
|
||||
}
|
||||
```
|
||||
|
174
README.md
174
README.md
@@ -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)
|
||||
===
|
||||
|
||||
@@ -108,88 +110,96 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
||||
-->
|
||||
|
||||
* [16mhz](https://api.github.com/users/16mhz)
|
||||
* [Ahiles3005](https://api.github.com/users/Ahiles3005)
|
||||
* [Albirew](https://api.github.com/users/Albirew)
|
||||
* [AmauryCarrade](https://api.github.com/users/AmauryCarrade)
|
||||
* [ArthurHoaro](https://api.github.com/users/ArthurHoaro)
|
||||
* [Astalaseven](https://api.github.com/users/Astalaseven)
|
||||
* [Astyan-42](https://api.github.com/users/Astyan-42)
|
||||
* [Daiyousei](https://api.github.com/users/Daiyousei)
|
||||
* [Djuuu](https://api.github.com/users/Djuuu)
|
||||
* [Draeli](https://api.github.com/users/Draeli)
|
||||
* [EtienneM](https://api.github.com/users/EtienneM)
|
||||
* [Frenzie](https://api.github.com/users/Frenzie)
|
||||
* [Ginko-Aloe](https://api.github.com/users/Ginko-Aloe)
|
||||
* [Glandos](https://api.github.com/users/Glandos)
|
||||
* [GregThib](https://api.github.com/users/GregThib)
|
||||
* [Grummfy](https://api.github.com/users/Grummfy)
|
||||
* [JackNUMBER](https://api.github.com/users/JackNUMBER)
|
||||
* [JeremyRand](https://api.github.com/users/JeremyRand)
|
||||
* [Jocker666z](https://api.github.com/users/Jocker666z)
|
||||
* [LogMANOriginal](https://api.github.com/users/LogMANOriginal)
|
||||
* [MonsieurPoutounours](https://api.github.com/users/MonsieurPoutounours)
|
||||
* [ORelio](https://api.github.com/users/ORelio)
|
||||
* [PaulVayssiere](https://api.github.com/users/PaulVayssiere)
|
||||
* [Piranhaplant](https://api.github.com/users/Piranhaplant)
|
||||
* [Riduidel](https://api.github.com/users/Riduidel)
|
||||
* [Strubbl](https://api.github.com/users/Strubbl)
|
||||
* [TheRadialActive](https://api.github.com/users/TheRadialActive)
|
||||
* [TwizzyDizzy](https://api.github.com/users/TwizzyDizzy)
|
||||
* [WalterBarrett](https://api.github.com/users/WalterBarrett)
|
||||
* [ZeNairolf](https://api.github.com/users/ZeNairolf)
|
||||
* [adamchainz](https://api.github.com/users/adamchainz)
|
||||
* [aledeg](https://api.github.com/users/aledeg)
|
||||
* [alexAubin](https://api.github.com/users/alexAubin)
|
||||
* [az5he6ch](https://api.github.com/users/az5he6ch)
|
||||
* [b1nj](https://api.github.com/users/b1nj)
|
||||
* [benasse](https://api.github.com/users/benasse)
|
||||
* [captn3m0](https://api.github.com/users/captn3m0)
|
||||
* [chemel](https://api.github.com/users/chemel)
|
||||
* [ckiw](https://api.github.com/users/ckiw)
|
||||
* [cnlpete](https://api.github.com/users/cnlpete)
|
||||
* [corenting](https://api.github.com/users/corenting)
|
||||
* [da2x](https://api.github.com/users/da2x)
|
||||
* [eMerzh](https://api.github.com/users/eMerzh)
|
||||
* [em92](https://api.github.com/users/em92)
|
||||
* [griffaurel](https://api.github.com/users/griffaurel)
|
||||
* [hunhejj](https://api.github.com/users/hunhejj)
|
||||
* [j0k3r](https://api.github.com/users/j0k3r)
|
||||
* [jdigilio](https://api.github.com/users/jdigilio)
|
||||
* [kranack](https://api.github.com/users/kranack)
|
||||
* [kraoc](https://api.github.com/users/kraoc)
|
||||
* [laBecasse](https://api.github.com/users/laBecasse)
|
||||
* [lagaisse](https://api.github.com/users/lagaisse)
|
||||
* [lalannev](https://api.github.com/users/lalannev)
|
||||
* [ldidry](https://api.github.com/users/ldidry)
|
||||
* [m0zes](https://api.github.com/users/m0zes)
|
||||
* [matthewseal](https://api.github.com/users/matthewseal)
|
||||
* [mcbyte-it](https://api.github.com/users/mcbyte-it)
|
||||
* [mdemoss](https://api.github.com/users/mdemoss)
|
||||
* [melangue](https://api.github.com/users/melangue)
|
||||
* [metaMMA](https://api.github.com/users/metaMMA)
|
||||
* [mickael-bertrand](https://api.github.com/users/mickael-bertrand)
|
||||
* [mitsukarenai](https://api.github.com/users/mitsukarenai)
|
||||
* [mro](https://api.github.com/users/mro)
|
||||
* [mxmehl](https://api.github.com/users/mxmehl)
|
||||
* [nel50n](https://api.github.com/users/nel50n)
|
||||
* [niawag](https://api.github.com/users/niawag)
|
||||
* [pellaeon](https://api.github.com/users/pellaeon)
|
||||
* [pit-fgfjiudghdf](https://api.github.com/users/pit-fgfjiudghdf)
|
||||
* [pitchoule](https://api.github.com/users/pitchoule)
|
||||
* [pmaziere](https://api.github.com/users/pmaziere)
|
||||
* [prysme01](https://api.github.com/users/prysme01)
|
||||
* [quentinus95](https://api.github.com/users/quentinus95)
|
||||
* [qwertygc](https://api.github.com/users/qwertygc)
|
||||
* [regisenguehard](https://api.github.com/users/regisenguehard)
|
||||
* [rogerdc](https://api.github.com/users/rogerdc)
|
||||
* [sebsauvage](https://api.github.com/users/sebsauvage)
|
||||
* [sublimz](https://api.github.com/users/sublimz)
|
||||
* [sysadminstory](https://api.github.com/users/sysadminstory)
|
||||
* [tameroski](https://api.github.com/users/tameroski)
|
||||
* [teromene](https://api.github.com/users/teromene)
|
||||
* [triatic](https://api.github.com/users/triatic)
|
||||
* [wtuuju](https://api.github.com/users/wtuuju)
|
||||
* [16mhz](https://github.com/16mhz)
|
||||
* [Ahiles3005](https://github.com/Ahiles3005)
|
||||
* [Albirew](https://github.com/Albirew)
|
||||
* [AmauryCarrade](https://github.com/AmauryCarrade)
|
||||
* [AntoineTurmel](https://github.com/AntoineTurmel)
|
||||
* [ArthurHoaro](https://github.com/ArthurHoaro)
|
||||
* [Astalaseven](https://github.com/Astalaseven)
|
||||
* [Astyan-42](https://github.com/Astyan-42)
|
||||
* [Daiyousei](https://github.com/Daiyousei)
|
||||
* [Djuuu](https://github.com/Djuuu)
|
||||
* [Draeli](https://github.com/Draeli)
|
||||
* [EtienneM](https://github.com/EtienneM)
|
||||
* [Frenzie](https://github.com/Frenzie)
|
||||
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
|
||||
* [Glandos](https://github.com/Glandos)
|
||||
* [GregThib](https://github.com/GregThib)
|
||||
* [Grummfy](https://github.com/Grummfy)
|
||||
* [JackNUMBER](https://github.com/JackNUMBER)
|
||||
* [JeremyRand](https://github.com/JeremyRand)
|
||||
* [Jocker666z](https://github.com/Jocker666z)
|
||||
* [LogMANOriginal](https://github.com/LogMANOriginal)
|
||||
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
|
||||
* [Nono-m0le](https://github.com/Nono-m0le)
|
||||
* [ORelio](https://github.com/ORelio)
|
||||
* [PaulVayssiere](https://github.com/PaulVayssiere)
|
||||
* [Piranhaplant](https://github.com/Piranhaplant)
|
||||
* [Riduidel](https://github.com/Riduidel)
|
||||
* [Roliga](https://github.com/Roliga)
|
||||
* [Strubbl](https://github.com/Strubbl)
|
||||
* [TheRadialActive](https://github.com/TheRadialActive)
|
||||
* [TwizzyDizzy](https://github.com/TwizzyDizzy)
|
||||
* [WalterBarrett](https://github.com/WalterBarrett)
|
||||
* [ZeNairolf](https://github.com/ZeNairolf)
|
||||
* [adamchainz](https://github.com/adamchainz)
|
||||
* [aledeg](https://github.com/aledeg)
|
||||
* [alexAubin](https://github.com/alexAubin)
|
||||
* [az5he6ch](https://github.com/az5he6ch)
|
||||
* [b1nj](https://github.com/b1nj)
|
||||
* [benasse](https://github.com/benasse)
|
||||
* [captn3m0](https://github.com/captn3m0)
|
||||
* [chemel](https://github.com/chemel)
|
||||
* [ckiw](https://github.com/ckiw)
|
||||
* [cnlpete](https://github.com/cnlpete)
|
||||
* [corenting](https://github.com/corenting)
|
||||
* [couraudt](https://github.com/couraudt)
|
||||
* [da2x](https://github.com/da2x)
|
||||
* [disk0x](https://github.com/disk0x)
|
||||
* [eMerzh](https://github.com/eMerzh)
|
||||
* [em92](https://github.com/em92)
|
||||
* [fluffy-critter](https://github.com/fluffy-critter)
|
||||
* [griffaurel](https://github.com/griffaurel)
|
||||
* [hunhejj](https://github.com/hunhejj)
|
||||
* [j0k3r](https://github.com/j0k3r)
|
||||
* [jdigilio](https://github.com/jdigilio)
|
||||
* [kranack](https://github.com/kranack)
|
||||
* [kraoc](https://github.com/kraoc)
|
||||
* [laBecasse](https://github.com/laBecasse)
|
||||
* [lagaisse](https://github.com/lagaisse)
|
||||
* [lalannev](https://github.com/lalannev)
|
||||
* [ldidry](https://github.com/ldidry)
|
||||
* [m0zes](https://github.com/m0zes)
|
||||
* [matthewseal](https://github.com/matthewseal)
|
||||
* [mcbyte-it](https://github.com/mcbyte-it)
|
||||
* [mdemoss](https://github.com/mdemoss)
|
||||
* [melangue](https://github.com/melangue)
|
||||
* [metaMMA](https://github.com/metaMMA)
|
||||
* [mickael-bertrand](https://github.com/mickael-bertrand)
|
||||
* [mitsukarenai](https://github.com/mitsukarenai)
|
||||
* [mr-flibble](https://github.com/mr-flibble)
|
||||
* [mro](https://github.com/mro)
|
||||
* [mxmehl](https://github.com/mxmehl)
|
||||
* [nel50n](https://github.com/nel50n)
|
||||
* [niawag](https://github.com/niawag)
|
||||
* [pellaeon](https://github.com/pellaeon)
|
||||
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
|
||||
* [pitchoule](https://github.com/pitchoule)
|
||||
* [pmaziere](https://github.com/pmaziere)
|
||||
* [prysme01](https://github.com/prysme01)
|
||||
* [quentinus95](https://github.com/quentinus95)
|
||||
* [qwertygc](https://github.com/qwertygc)
|
||||
* [regisenguehard](https://github.com/regisenguehard)
|
||||
* [rogerdc](https://github.com/rogerdc)
|
||||
* [sebsauvage](https://github.com/sebsauvage)
|
||||
* [sublimz](https://github.com/sublimz)
|
||||
* [sysadminstory](https://github.com/sysadminstory)
|
||||
* [tameroski](https://github.com/tameroski)
|
||||
* [teromene](https://github.com/teromene)
|
||||
* [triatic](https://github.com/triatic)
|
||||
* [wtuuju](https://github.com/wtuuju)
|
||||
* [yardenac](https://github.com/yardenac)
|
||||
|
||||
Licenses
|
||||
===
|
||||
|
@@ -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());
|
||||
|
||||
|
227
bridges/CrewbayBridge.php
Normal file
227
bridges/CrewbayBridge.php
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
class CrewbayBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'couraudt';
|
||||
const NAME = 'Crewbay Bridge';
|
||||
const URI = 'https://www.crewbay.com';
|
||||
const DESCRIPTION = 'Returns the newest sailing offers.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'keyword' => array(
|
||||
'name' => 'Filter by keyword',
|
||||
'title' => 'Enter the keyword to filter here'
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Type of search',
|
||||
'title' => 'Choose between finding a boat or a crew',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Find a boat' => 'boats',
|
||||
'Find a crew' => 'crew'
|
||||
)
|
||||
),
|
||||
'status' => array(
|
||||
'name' => 'Status on the boat',
|
||||
'title' => 'Choose between recreational or professional classified ads',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Recreational' => 'recreational',
|
||||
'Professional' => 'professional'
|
||||
)
|
||||
),
|
||||
'recreational_position' => array(
|
||||
'name' => 'Recreational position wanted',
|
||||
'title' => 'Filter by recreational position you wanted aboard',
|
||||
'required' => false,
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'Amateur Crew' => 'Amateur Crew',
|
||||
'Friendship' => 'Friendship',
|
||||
'Competent Crew' => 'Competent Crew',
|
||||
'Racing' => 'Racing',
|
||||
'Voluntary work' => 'Voluntary work',
|
||||
'Mile building' => 'Mile building'
|
||||
)
|
||||
),
|
||||
'professional_position' => array(
|
||||
'name' => 'Professional position wanted',
|
||||
'title' => 'Filter by professional position you wanted aboard',
|
||||
'required' => false,
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'1st Engineer' => '1st Engineer',
|
||||
'1st Mate' => '1st Mate',
|
||||
'Beautician' => 'Beautician',
|
||||
'Bosun' => 'Bosun',
|
||||
'Captain' => 'Captain',
|
||||
'Chef' => 'Chef',
|
||||
'Steward(ess)' => 'Steward(ess)',
|
||||
'Deckhand' => 'Deckhand',
|
||||
'Delivery Crew' => 'Delivery Crew',
|
||||
'Dive Instructor' => 'Dive Instructor',
|
||||
'Masseur' => 'Masseur',
|
||||
'Medical Staff' => 'Medical Staff',
|
||||
'Nanny' => 'Nanny',
|
||||
'Navigator' => 'Navigator',
|
||||
'Racing Crew' => 'Racing Crew',
|
||||
'Teacher' => 'Teacher',
|
||||
'Electrical Engineer' => 'Electrical Engineer',
|
||||
'Fitter' => 'Fitter',
|
||||
'2nd Engineer' => '2nd Engineer',
|
||||
'3rd Engineer' => '3rd Engineer',
|
||||
'Lead Deckhand' => 'Lead Deckhand',
|
||||
'Security Officer' => 'Security Officer',
|
||||
'O.O.W' => 'O.O.W',
|
||||
'1st Officer' => '1st Officer',
|
||||
'2nd Officer' => '2nd Officer',
|
||||
'3rd Officer' => '3rd Officer',
|
||||
'Captain/Engineer' => 'Captain/Engineer',
|
||||
'Hairdresser' => 'Hairdresser',
|
||||
'Fitness Trainer' => 'Fitness Trainer',
|
||||
'Laundry' => 'Laundry',
|
||||
'Solo Steward/ess' => 'Solo Steward/ess',
|
||||
'Stew/Deck' => 'Stew/Deck',
|
||||
'2nd Steward/ess' => '2nd Steward/ess',
|
||||
'3rd Steward/ess' => '3rd Steward/ess',
|
||||
'Chief Steward/ess' => 'Chief Steward/ess',
|
||||
'Head Housekeeper' => 'Head Housekeeper',
|
||||
'Purser' => 'Purser',
|
||||
'Cook' => 'Cook',
|
||||
'Cook/Stew' => 'Cook/Stew',
|
||||
'2nd Chef' => '2nd Chef',
|
||||
'Head Chef' => 'Head Chef',
|
||||
'Administrator' => 'Administrator',
|
||||
'P.A' => 'P.A',
|
||||
'Villa staff' => 'Villa staff',
|
||||
'Housekeeping/Stew' => 'Housekeeping/Stew',
|
||||
'Stew/Beautician' => 'Stew/Beautician',
|
||||
'Stew/Masseuse' => 'Stew/Masseuse',
|
||||
'Manager' => 'Manager',
|
||||
'Sailing instructor' => 'Sailing instructor'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getURI();
|
||||
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
|
||||
|
||||
$annonces = $html->find('#SearchResults div.result');
|
||||
$limit = 0;
|
||||
|
||||
foreach ($annonces as $annonce) {
|
||||
$detail = $annonce->find('.btn--profile', 0);
|
||||
$htmlDetail = getSimpleHTMLDOMCached($detail->href);
|
||||
|
||||
if (!empty($this->getInput('recreational_position')) || !empty($this->getInput('professional_position'))) {
|
||||
if ($this->getInput('type') == 'boats') {
|
||||
if ($this->getInput('status') == 'professional') {
|
||||
$positions = array($annonce->find('.title .position', 0)->plaintext);
|
||||
} else {
|
||||
$positions = array(str_replace('Wanted:', '', $annonce->find('.content li', 0)->plaintext));
|
||||
}
|
||||
} else {
|
||||
$list = $htmlDetail->find('.viewer-details .viewer-list');
|
||||
$positions = explode("\r\n", end($list)->find('span.value', 0)->plaintext);
|
||||
}
|
||||
|
||||
$found = false;
|
||||
$keyword = $this->getInput('status') == 'professional' ? 'professional_position' : 'recreational_position';
|
||||
foreach ($positions as $position) {
|
||||
if (strpos(trim($position), $this->getInput($keyword)) !== false) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$item = array();
|
||||
|
||||
if ($this->getInput('type') == 'boats') {
|
||||
$titleSelector = '.title h2';
|
||||
} else {
|
||||
$titleSelector = '.layout__item h2';
|
||||
}
|
||||
$userName = $annonce->find('.result--description a', 0)->plaintext;
|
||||
$annonceTitle = trim($annonce->find($titleSelector, 0)->plaintext);
|
||||
if (empty($annonceTitle)) {
|
||||
$item['title'] = $userName;
|
||||
} else {
|
||||
$item['title'] = $userName . ' - ' . $annonceTitle;
|
||||
}
|
||||
|
||||
$item['uri'] = $detail->href;
|
||||
$images = $annonce->find('.avatar img');
|
||||
$item['enclosures'] = array(end($images)->getAttribute('src'));
|
||||
|
||||
$content = $htmlDetail->find('.viewer-intro--info', 0)->innertext;
|
||||
|
||||
$sections = $htmlDetail->find('.viewer-container .viewer-section');
|
||||
foreach ($sections as $section) {
|
||||
if ($section->find('.viewer-section-title', 0)) {
|
||||
$class = str_replace('viewer-', '', explode(' ', $section->getAttribute('class'))[0]);
|
||||
if (!in_array($class, array('apply', 'photos', 'reviews', 'contact', 'experience', 'qa'))) {
|
||||
// Basic sections
|
||||
$content .= $section->find('.viewer-section-title h3', 0)->outertext;
|
||||
$content .= $section->find('.viewer-section-content', 0)->innertext;
|
||||
}
|
||||
} else {
|
||||
// Info section
|
||||
$content .= $section->find('.viewer-section-content h3', 0)->outertext;
|
||||
$content .= $section->find('.viewer-section-content p', 0)->outertext;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->getInput('keyword'))) {
|
||||
$keyword = strtolower($this->getInput('keyword'));
|
||||
if (strpos(strtolower($item['title']), $keyword) === false) {
|
||||
if (strpos(strtolower($content), $keyword) === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$item['content'] = $content;
|
||||
|
||||
$tags = $htmlDetail->find('li.viewer-tags--tag');
|
||||
foreach ($tags as $tag) {
|
||||
if (!isset($item['categories'])) {
|
||||
$item['categories'] = array();
|
||||
}
|
||||
$text = trim($tag->plaintext);
|
||||
if (!in_array($text, $item['categories'])) {
|
||||
$item['categories'][] = $text;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
$limit += 1;
|
||||
|
||||
if ($limit == 10) break;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = parent::getURI();
|
||||
|
||||
if ($this->getInput('type') == 'boats') {
|
||||
$uri .= '/boats';
|
||||
} else {
|
||||
$uri .= '/crew';
|
||||
}
|
||||
|
||||
if ($this->getInput('status') == 'professional') {
|
||||
$uri .= '/professional';
|
||||
} else {
|
||||
$uri .= '/recreational';
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
}
|
@@ -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 _78cz _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];
|
||||
|
||||
@@ -616,14 +659,8 @@ EOD;
|
||||
$date = 0;
|
||||
}
|
||||
|
||||
// Build title from username and content
|
||||
$title = $author;
|
||||
|
||||
if(strlen($title) > 24)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 24), "\n")) . '...';
|
||||
|
||||
$title = $title . ' | ' . strip_tags($content);
|
||||
|
||||
// Build title from content
|
||||
$title = strip_tags($post->find('.userContent', 0)->innertext);
|
||||
if(strlen($title) > 64)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
|
||||
|
||||
@@ -634,10 +671,10 @@ EOD;
|
||||
}
|
||||
|
||||
//Build and add final item
|
||||
$item['uri'] = htmlspecialchars_decode($uri);
|
||||
$item['content'] = htmlspecialchars_decode($content);
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['uri'] = htmlspecialchars_decode($uri, ENT_QUOTES);
|
||||
$item['content'] = htmlspecialchars_decode($content, ENT_QUOTES);
|
||||
$item['title'] = htmlspecialchars_decode($title, ENT_QUOTES);
|
||||
$item['author'] = htmlspecialchars_decode($author, ENT_QUOTES);
|
||||
$item['timestamp'] = $date;
|
||||
|
||||
if(strpos($item['content'], '<img') === false) {
|
||||
|
@@ -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)
|
||||
|
@@ -94,7 +94,7 @@ class FilterBridge extends FeedExpander {
|
||||
}
|
||||
try{
|
||||
$this->collectExpandableDatas($this->getURI());
|
||||
} catch (HttpException $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->collectExpandableDatas($this->getURI());
|
||||
}
|
||||
}
|
||||
|
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>';
|
||||
|
@@ -37,10 +37,9 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
$name = $this->getInput('u') . '/' . $this->getInput('p');
|
||||
switch($this->queriedContext) {
|
||||
case 'Project Issues':
|
||||
$prefix = static::NAME . 's for ';
|
||||
if($this->getInput('c')) {
|
||||
$prefix = static::NAME . 's comments for ';
|
||||
} else {
|
||||
$prefix = static::NAME . 's for ';
|
||||
}
|
||||
$name = $prefix . $name;
|
||||
break;
|
||||
@@ -53,8 +52,9 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u')) && !is_null($this->getInput('p'))) {
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues';
|
||||
if(null !== $this->getInput('u') && null !== $this->getInput('p')) {
|
||||
$uri = static::URI . $this->getInput('u') . '/'
|
||||
. $this->getInput('p') . '/issues';
|
||||
if($this->queriedContext === 'Issue comments') {
|
||||
$uri .= '/' . $this->getInput('i');
|
||||
} elseif($this->getInput('c')) {
|
||||
@@ -66,54 +66,54 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
protected function extractIssueComment($issueNbr, $title, $comment){
|
||||
$class = $comment->getAttribute('class');
|
||||
$classes = explode(' ', $class);
|
||||
$event = false;
|
||||
if(in_array('discussion-item', $classes)) {
|
||||
$event = true;
|
||||
}
|
||||
|
||||
$author = 'unknown';
|
||||
if($comment->find('.author', 0)) {
|
||||
$author = $comment->find('.author', 0)->plaintext;
|
||||
}
|
||||
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
|
||||
protected function extractIssueEvent($issueNbr, $title, $comment){
|
||||
$comment = $comment->firstChild();
|
||||
if(!$event) {
|
||||
$comment = $comment->nextSibling();
|
||||
}
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p')
|
||||
. '/issues/' . $issueNbr . '#' . $comment->getAttribute('id');
|
||||
|
||||
if($event) {
|
||||
$title .= ' / ' . substr($class, strpos($class, 'discussion-item-') + strlen('discussion-item-'));
|
||||
if(!$comment->hasAttribute('id')) {
|
||||
$items = array();
|
||||
$timestamp = strtotime($comment->find('relative-time', 0)->getAttribute('datetime'));
|
||||
$content = $comment->innertext;
|
||||
while($comment = $comment->nextSibling()) {
|
||||
$item = array();
|
||||
$item['author'] = $author;
|
||||
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['content'] = $content . '<p>' . $comment->children(1)->innertext . '</p>';
|
||||
$item['uri'] = $uri . '#' . $comment->children(1)->getAttribute('id');
|
||||
$items[] = $item;
|
||||
}
|
||||
return $items;
|
||||
$author = $comment->find('.author', 0)->plaintext;
|
||||
|
||||
$title .= ' / ' . trim($comment->plaintext);
|
||||
|
||||
$content = $title;
|
||||
if (null !== $comment->nextSibling()) {
|
||||
$content = $comment->nextSibling()->innertext;
|
||||
if ($comment->nextSibling()->nodeName() === 'span') {
|
||||
$content = $comment->nextSibling()->nextSibling()->innertext;
|
||||
}
|
||||
$content = $comment->parent()->innertext;
|
||||
} else {
|
||||
$title .= ' / ' . trim($comment->firstChild()->plaintext);
|
||||
$content = '<pre>' . $comment->find('.comment-body', 0)->innertext . '</pre>';
|
||||
}
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $author;
|
||||
$item['uri'] = $uri . '#' . $comment->getAttribute('id');
|
||||
$item['uri'] = $uri;
|
||||
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
|
||||
$item['timestamp'] = strtotime($comment->find('relative-time', 0)->getAttribute('datetime'));
|
||||
$item['timestamp'] = strtotime(
|
||||
$comment->find('relative-time', 0)->getAttribute('datetime')
|
||||
);
|
||||
$item['content'] = $content;
|
||||
return $item;
|
||||
}
|
||||
|
||||
protected function extractIssueComment($issueNbr, $title, $comment){
|
||||
$uri = static::URI . $this->getInput('u') . '/'
|
||||
. $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
|
||||
$author = $comment->find('.author', 0)->plaintext;
|
||||
|
||||
$title .= ' / ' . trim(
|
||||
$comment->find('.comment .timeline-comment-header-text', 0)->plaintext
|
||||
);
|
||||
|
||||
$content = $comment->find('.comment-body', 0)->innertext;
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $author;
|
||||
$item['uri'] = $uri
|
||||
. '#' . $comment->firstChild()->nextSibling()->getAttribute('id');
|
||||
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
|
||||
$item['timestamp'] = strtotime(
|
||||
$comment->find('relative-time', 0)->getAttribute('datetime')
|
||||
);
|
||||
$item['content'] = $content;
|
||||
return $item;
|
||||
}
|
||||
@@ -121,17 +121,29 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
protected function extractIssueComments($issue){
|
||||
$items = array();
|
||||
$title = $issue->find('.gh-header-title', 0)->plaintext;
|
||||
$issueNbr = trim(substr($issue->find('.gh-header-number', 0)->plaintext, 1));
|
||||
$issueNbr = trim(
|
||||
substr($issue->find('.gh-header-number', 0)->plaintext, 1)
|
||||
);
|
||||
$comments = $issue->find('.js-discussion', 0);
|
||||
foreach($comments->children() as $comment) {
|
||||
if (!$comment->hasChildNodes()) {
|
||||
continue;
|
||||
}
|
||||
$comment = $comment->firstChild();
|
||||
$classes = explode(' ', $comment->getAttribute('class'));
|
||||
if(in_array('discussion-item', $classes)
|
||||
|| in_array('timeline-comment-wrapper', $classes)) {
|
||||
if (in_array('timeline-comment-wrapper', $classes)) {
|
||||
$item = $this->extractIssueComment($issueNbr, $title, $comment);
|
||||
if(array_keys($item) !== range(0, count($item) - 1)) {
|
||||
$item = array($item);
|
||||
$items[] = $item;
|
||||
continue;
|
||||
}
|
||||
while (in_array('discussion-item', $classes)) {
|
||||
$item = $this->extractIssueEvent($issueNbr, $title, $comment);
|
||||
$items[] = $item;
|
||||
$comment = $comment->nextSibling();
|
||||
if (null == $comment) {
|
||||
break;
|
||||
}
|
||||
$items = array_merge($items, $item);
|
||||
$classes = explode(' ', $comment->getAttribute('class'));
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
@@ -139,7 +151,9 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('No results for Github Issue ' . $this->getURI());
|
||||
or returnServerError(
|
||||
'No results for Github Issue ' . $this->getURI()
|
||||
);
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'Issue comments':
|
||||
@@ -148,31 +162,40 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
case 'Project Issues':
|
||||
foreach($html->find('.js-active-navigation-container .js-navigation-item') as $issue) {
|
||||
$info = $issue->find('.opened-by', 0);
|
||||
$issueNbr = substr(trim($info->plaintext), 1, strpos(trim($info->plaintext), ' '));
|
||||
$issueNbr = substr(
|
||||
trim($info->plaintext), 1, strpos(trim($info->plaintext), ' ')
|
||||
);
|
||||
|
||||
$item = array();
|
||||
$item['content'] = '';
|
||||
|
||||
if($this->getInput('c')) {
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
$uri = static::URI . $this->getInput('u')
|
||||
. '/' . $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
$issue = getSimpleHTMLDOMCached($uri, static::CACHE_TIMEOUT);
|
||||
if($issue) {
|
||||
$this->items = array_merge($this->items, $this->extractIssueComments($issue));
|
||||
$this->items = array_merge(
|
||||
$this->items,
|
||||
$this->extractIssueComments($issue)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$item['content'] = 'Can not extract comments from ' . $uri;
|
||||
}
|
||||
|
||||
$item['author'] = $info->find('a', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($info->find('relative-time', 0)->getAttribute('datetime'));
|
||||
$item['timestamp'] = strtotime(
|
||||
$info->find('relative-time', 0)->getAttribute('datetime')
|
||||
);
|
||||
$item['title'] = html_entity_decode(
|
||||
$issue->find('.js-navigation-open', 0)->plaintext,
|
||||
ENT_QUOTES,
|
||||
'UTF-8'
|
||||
);
|
||||
$comments = $issue->find('.col-5', 0)->plaintext;
|
||||
$comments = trim($issue->find('.col-5', 0)->plaintext);
|
||||
$item['content'] .= "\n" . 'Comments: ' . ($comments ? $comments : '0');
|
||||
$item['uri'] = self::URI . $issue->find('.js-navigation-open', 0)->getAttribute('href');
|
||||
$item['uri'] = self::URI
|
||||
. $issue->find('.js-navigation-open', 0)->getAttribute('href');
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
@@ -180,7 +203,11 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
|
||||
array_walk($this->items, function(&$item){
|
||||
$item['content'] = preg_replace('/\s+/', ' ', $item['content']);
|
||||
$item['content'] = str_replace('href="/', 'href="' . static::URI, $item['content']);
|
||||
$item['content'] = str_replace(
|
||||
'href="/',
|
||||
'href="' . static::URI,
|
||||
$item['content']
|
||||
);
|
||||
$item['content'] = str_replace(
|
||||
'href="#',
|
||||
'href="' . substr($item['uri'], 0, strpos($item['uri'], '#') + 1),
|
||||
|
0
bridges/GlassdoorBridge.php
Executable file → Normal file
0
bridges/GlassdoorBridge.php
Executable file → Normal file
@@ -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) {
|
||||
|
@@ -132,7 +132,7 @@ class JustETFBridge extends BridgeAbstract {
|
||||
|
||||
date_time_set($df, 0, 0);
|
||||
|
||||
// debugMessage(date_format($df, 'U'));
|
||||
// Debug::log(date_format($df, 'U'));
|
||||
|
||||
return date_format($df, 'U');
|
||||
}
|
||||
@@ -210,7 +210,7 @@ class JustETFBridge extends BridgeAbstract {
|
||||
$element = $article->find('div.subheadline', 0)
|
||||
or returnServerError('Date not found!');
|
||||
|
||||
// debugMessage($element->plaintext);
|
||||
// Debug::log($element->plaintext);
|
||||
|
||||
$date = trim(explode('|', $element->plaintext)[0]);
|
||||
|
||||
@@ -223,7 +223,7 @@ class JustETFBridge extends BridgeAbstract {
|
||||
|
||||
$element->find('a', 0)->onclick = '';
|
||||
|
||||
// debugMessage($element->innertext);
|
||||
// Debug::log($element->innertext);
|
||||
|
||||
return $element->innertext;
|
||||
}
|
||||
@@ -288,7 +288,7 @@ class JustETFBridge extends BridgeAbstract {
|
||||
$element = $html->find('div.infobox div.vallabel', 0)
|
||||
or returnServerError('Date not found!');
|
||||
|
||||
// debugMessage($element->plaintext);
|
||||
// Debug::log($element->plaintext);
|
||||
|
||||
$date = trim(explode("\r\n", $element->plaintext)[1]);
|
||||
|
||||
|
@@ -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);
|
||||
|
28
bridges/MozillaSecurity.php
Normal file
28
bridges/MozillaSecurity.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
class MozillaSecurityBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'm0le.net';
|
||||
const NAME = 'Mozilla Security Advisories';
|
||||
const URI = 'https://www.mozilla.org/en-US/security/advisories/';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Mozilla Security Advisories';
|
||||
const WEBROOT = 'https://www.mozilla.org';
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request MSA.');
|
||||
|
||||
$html = defaultLinkTo($html, self::WEBROOT);
|
||||
|
||||
$item = array();
|
||||
$articles = $html->find('div[itemprop="articleBody"] h2');
|
||||
|
||||
foreach ($articles as $element) {
|
||||
$item['title'] = $element->innertext;
|
||||
$item['timestamp'] = strtotime($element->innertext);
|
||||
$item['content'] = $element->next_sibling()->innertext;
|
||||
$item['uri'] = self::URI;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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');
|
||||
}
|
||||
}
|
||||
|
@@ -34,13 +34,13 @@ class SoundCloudBridge extends BridgeAbstract {
|
||||
|
||||
for($i = 0; $i < 10; $i++) {
|
||||
$item = array();
|
||||
$item['author'] = $tracks[$i]->user->username . ' - ' . $tracks[$i]->title;
|
||||
$item['author'] = $tracks[$i]->user->username;
|
||||
$item['title'] = $tracks[$i]->user->username . ' - ' . $tracks[$i]->title;
|
||||
$item['content'] = '<audio src="'
|
||||
. $tracks[$i]->uri
|
||||
$item['timestamp'] = strtotime($tracks[$i]->created_at);
|
||||
$item['content'] = $tracks[$i]->description;
|
||||
$item['enclosures'] = array($tracks[$i]->uri
|
||||
. '/stream?client_id='
|
||||
. self::CLIENT_ID
|
||||
. '">';
|
||||
. self::CLIENT_ID);
|
||||
|
||||
$item['id'] = self::URI
|
||||
. urlencode($this->getInput('u'))
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
}
|
@@ -66,6 +66,41 @@ class TwitterBridge extends BridgeAbstract {
|
||||
)
|
||||
);
|
||||
|
||||
public function detectParameters($url){
|
||||
$params = array();
|
||||
|
||||
// By keyword or hashtag (search)
|
||||
$regex = '/^(https?:\/\/)?(www\.)?twitter\.com\/search.*(\?|&)q=([^\/&?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['q'] = urldecode($matches[4]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
// By hashtag
|
||||
$regex = '/^(https?:\/\/)?(www\.)?twitter\.com\/hashtag\/([^\/?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['q'] = urldecode($matches[3]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
// By list
|
||||
$regex = '/^(https?:\/\/)?(www\.)?twitter\.com\/([^\/?\n]+)\/lists\/([^\/?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['user'] = urldecode($matches[3]);
|
||||
$params['list'] = urldecode($matches[4]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
// By username
|
||||
$regex = '/^(https?:\/\/)?(www\.)?twitter\.com\/([^\/?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['u'] = urldecode($matches[3]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By keyword or hashtag':
|
||||
@@ -144,9 +179,9 @@ class TwitterBridge extends BridgeAbstract {
|
||||
|
||||
$item = array();
|
||||
// extract username and sanitize
|
||||
$item['username'] = $tweet->getAttribute('data-screen-name');
|
||||
$item['username'] = htmlspecialchars_decode($tweet->getAttribute('data-screen-name'), ENT_QUOTES);
|
||||
// extract fullname (pseudonym)
|
||||
$item['fullname'] = $tweet->getAttribute('data-name');
|
||||
$item['fullname'] = htmlspecialchars_decode($tweet->getAttribute('data-name'), ENT_QUOTES);
|
||||
// get author
|
||||
$item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')';
|
||||
// get avatar link
|
||||
@@ -158,7 +193,8 @@ class TwitterBridge extends BridgeAbstract {
|
||||
// extract tweet timestamp
|
||||
$item['timestamp'] = $tweet->find('span.js-short-timestamp', 0)->getAttribute('data-time');
|
||||
// generate the title
|
||||
$item['title'] = strip_tags($this->fixAnchorSpacing($tweet->find('p.js-tweet-text', 0), '<a>'));
|
||||
$item['title'] = strip_tags($this->fixAnchorSpacing(htmlspecialchars_decode(
|
||||
$tweet->find('p.js-tweet-text', 0), ENT_QUOTES), '<a>'));
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'By list':
|
||||
@@ -258,16 +294,17 @@ EOD;
|
||||
}
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
{$item['content']}
|
||||
<hr>
|
||||
<div style="display: inline-block; vertical-align: top;">
|
||||
<blockquote>{$cleanedQuotedTweet}</blockquote>
|
||||
</div>
|
||||
<div style="display: block; vertical-align: top;">
|
||||
<blockquote>{$quotedImage_html}</blockquote>
|
||||
</div>
|
||||
<hr>
|
||||
{$item['content']}
|
||||
EOD;
|
||||
}
|
||||
$item['content'] = htmlspecialchars_decode($item['content'], ENT_QUOTES);
|
||||
|
||||
// put out
|
||||
$this->items[] = $item;
|
||||
|
@@ -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) {
|
||||
|
@@ -93,7 +93,7 @@ class WordPressBridge extends FeedExpander {
|
||||
}
|
||||
try{
|
||||
$this->collectExpandableDatas($this->getURI() . '/feed/atom/');
|
||||
} catch (HttpException $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->collectExpandableDatas($this->getURI() . '/?feed=atom');
|
||||
}
|
||||
|
||||
|
@@ -74,10 +74,10 @@ class WordPressPluginUpdateBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
private function getCachedDate($url){
|
||||
debugMessage('getting pubdate from url ' . $url . '');
|
||||
Debug::log('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;
|
||||
|
||||
}
|
||||
|
||||
// Debug::log(date_format($df, 'U'));
|
||||
|
||||
return date_format($df, 'U');
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -115,6 +115,7 @@ class YGGTorrentBridge extends BridgeAbstract {
|
||||
$item = array();
|
||||
$item['timestamp'] = $row->find('.hidden', 1)->plaintext;
|
||||
$item['title'] = $row->find('a', 1)->plaintext;
|
||||
$item['uri'] = $row->find('a', 1)->href;
|
||||
$torrentData = $this->collectTorrentData($row->find('a', 1)->href);
|
||||
$item['author'] = $torrentData['author'];
|
||||
$item['content'] = $torrentData['content'];
|
||||
|
@@ -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,8 +1,15 @@
|
||||
<?php
|
||||
class ZoneTelechargementBridge extends BridgeAbstract {
|
||||
const NAME = 'Zone Telechargement';
|
||||
const URI = 'https://ww4.zone-telechargement1.org/';
|
||||
const DESCRIPTION = 'Suivi de série sur Zone Telechargement';
|
||||
|
||||
/* This bridge was initally done for the Website Zone Telechargement,
|
||||
* but the website changed it's name and URL.
|
||||
* Therefore, the class name and filename does not correspond to the
|
||||
* name of the bridge. This permits to keep the same RSS Feed URL.
|
||||
*/
|
||||
|
||||
const NAME = 'Annuaire Telechargement';
|
||||
const URI = 'https://www.annuaire-telechargement.com/';
|
||||
const DESCRIPTION = 'Suivi de série sur Annuaire Telechargement';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
const PARAMETERS = array(
|
||||
'Suivre la publication des épisodes d\'une série en cours de diffusion' => array(
|
||||
@@ -10,11 +17,15 @@ class ZoneTelechargementBridge extends BridgeAbstract {
|
||||
'name' => 'URL de la série',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'URL d\'une série sans le https://ww4.zone-telechargement1.org/',
|
||||
'title' => 'URL d\'une série sans le https://www.annuaire-telechargement.com/',
|
||||
'exampleValue' => 'telecharger-series/31079-halt-and-catch-fire-saison-4-french-hd720p.html'
|
||||
)
|
||||
)
|
||||
);
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.annuaire-telechargement.com/templates/Default/images/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
|
||||
@@ -39,7 +50,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 +69,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 +79,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;
|
||||
}
|
||||
}
|
||||
|
164
index.php
164
index.php
@@ -1,40 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
Create a file named 'DEBUG' for enabling debug mode.
|
||||
For further security, you may put whitelisted IP addresses in the file,
|
||||
one IP per line. Empty file allows anyone(!).
|
||||
Debugging allows displaying PHP error messages and bypasses the cache: this
|
||||
can allow a malicious client to retrieve data about your server and hammer
|
||||
a provider throught your rss-bridge instance.
|
||||
*/
|
||||
if(file_exists('DEBUG')) {
|
||||
$debug_whitelist = trim(file_get_contents('DEBUG'));
|
||||
|
||||
$debug_enabled = empty($debug_whitelist)
|
||||
|| in_array($_SERVER['REMOTE_ADDR'],
|
||||
explode("\n", str_replace("\r", '', $debug_whitelist)
|
||||
)
|
||||
);
|
||||
|
||||
if($debug_enabled) {
|
||||
ini_set('display_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
define('DEBUG', true);
|
||||
if (empty($debug_whitelist)) {
|
||||
define('DEBUG_INSECURE', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
require_once __DIR__ . '/lib/rssbridge.php';
|
||||
|
||||
Configuration::verifyInstallation();
|
||||
Configuration::loadConfiguration();
|
||||
@@ -54,13 +19,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(
|
||||
@@ -69,7 +36,7 @@ $whitelist_default = array(
|
||||
'DansTonChatBridge',
|
||||
'DuckDuckGoBridge',
|
||||
'FacebookBridge',
|
||||
'FlickrExploreBridge',
|
||||
'FlickrBridge',
|
||||
'GooglePlusPostBridge',
|
||||
'GoogleSearchBridge',
|
||||
'IdenticaBridge',
|
||||
@@ -83,26 +50,7 @@ $whitelist_default = array(
|
||||
|
||||
try {
|
||||
|
||||
Bridge::setDir(__DIR__ . '/bridges/');
|
||||
Format::setDir(__DIR__ . '/formats/');
|
||||
Cache::setDir(__DIR__ . '/caches/');
|
||||
|
||||
if(!file_exists(WHITELIST_FILE)) {
|
||||
$whitelist_selection = $whitelist_default;
|
||||
$whitelist_write = implode("\n", $whitelist_default);
|
||||
file_put_contents(WHITELIST_FILE, $whitelist_write);
|
||||
} else {
|
||||
|
||||
$whitelist_file_content = file_get_contents(WHITELIST_FILE);
|
||||
if($whitelist_file_content != "*\n") {
|
||||
$whitelist_selection = explode("\n", $whitelist_file_content);
|
||||
} else {
|
||||
$whitelist_selection = Bridge::listBridges();
|
||||
}
|
||||
|
||||
// Prepare for case-insensitive match
|
||||
$whitelist_selection = array_map('strtolower', $whitelist_selection);
|
||||
}
|
||||
Bridge::setWhitelist($whitelist_default);
|
||||
|
||||
$showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN);
|
||||
$action = array_key_exists('action', $params) ? $params['action'] : null;
|
||||
@@ -115,7 +63,7 @@ try {
|
||||
$list->bridges = array();
|
||||
$list->total = 0;
|
||||
|
||||
foreach(Bridge::listBridges() as $bridgeName) {
|
||||
foreach(Bridge::getBridgeNames() as $bridgeName) {
|
||||
|
||||
$bridge = Bridge::create($bridgeName);
|
||||
|
||||
@@ -129,7 +77,7 @@ try {
|
||||
|
||||
}
|
||||
|
||||
$status = Bridge::isWhitelisted($whitelist_selection, strtolower($bridgeName)) ? 'active' : 'inactive';
|
||||
$status = Bridge::isWhitelisted($bridgeName) ? 'active' : 'inactive';
|
||||
|
||||
$list->bridges[$bridgeName] = array(
|
||||
'status' => $status,
|
||||
@@ -148,13 +96,44 @@ try {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($list, JSON_PRETTY_PRINT);
|
||||
|
||||
} elseif($action === 'display' && !empty($bridge)) {
|
||||
// DEPRECATED: 'nameBridge' scheme is replaced by 'name' in bridge parameter values
|
||||
// this is to keep compatibility until futher complete removal
|
||||
if(($pos = strpos($bridge, 'Bridge')) === (strlen($bridge) - strlen('Bridge'))) {
|
||||
$bridge = substr($bridge, 0, $pos);
|
||||
} elseif($action === 'detect') {
|
||||
|
||||
$targetURL = $params['url']
|
||||
or returnClientError('You must specify a url!');
|
||||
|
||||
$format = $params['format']
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
foreach(Bridge::getBridgeNames() as $bridgeName) {
|
||||
|
||||
if(!Bridge::isWhitelisted($bridgeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridge = Bridge::create($bridgeName);
|
||||
|
||||
if($bridge === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridgeParams = $bridge->detectParameters($targetURL);
|
||||
|
||||
if(is_null($bridgeParams)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridgeParams['bridge'] = $bridgeName;
|
||||
$bridgeParams['format'] = $format;
|
||||
|
||||
header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301);
|
||||
die();
|
||||
|
||||
}
|
||||
|
||||
returnClientError('No bridge found for given URL: ' . $targetURL);
|
||||
|
||||
} elseif($action === 'display' && !empty($bridge)) {
|
||||
|
||||
$format = $params['format']
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
@@ -165,8 +144,8 @@ try {
|
||||
}
|
||||
|
||||
// whitelist control
|
||||
if(!Bridge::isWhitelisted($whitelist_selection, strtolower($bridge))) {
|
||||
throw new \HttpException('This bridge is not whitelisted', 401);
|
||||
if(!Bridge::isWhitelisted($bridge)) {
|
||||
throw new \Exception('This bridge is not whitelisted', 401);
|
||||
die;
|
||||
}
|
||||
|
||||
@@ -183,7 +162,10 @@ try {
|
||||
if(array_key_exists('_cache_timeout', $params)) {
|
||||
|
||||
if(!CUSTOM_CACHE_TIMEOUT) {
|
||||
throw new \HttpException('This server doesn\'t support "_cache_timeout"!');
|
||||
unset($params['_cache_timeout']);
|
||||
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
|
||||
header('Location: ' . $uri, true, 301);
|
||||
die();
|
||||
}
|
||||
|
||||
$cache_timeout = filter_var($params['_cache_timeout'], FILTER_VALIDATE_INT);
|
||||
@@ -202,6 +184,7 @@ try {
|
||||
'format',
|
||||
'_noproxy',
|
||||
'_cache_timeout',
|
||||
'_error_time'
|
||||
), '')
|
||||
);
|
||||
|
||||
@@ -214,12 +197,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);
|
||||
|
||||
@@ -229,7 +213,7 @@ try {
|
||||
|
||||
if($mtime !== false
|
||||
&& (time() - $cache_timeout < $mtime)
|
||||
&& (!defined('DEBUG') || DEBUG !== true)) { // Load cached data
|
||||
&& !Debug::isEnabled()) { // Load cached data
|
||||
|
||||
// Send "Not Modified" response if client supports it
|
||||
// Implementation based on https://stackoverflow.com/a/10847262
|
||||
@@ -237,7 +221,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 +246,8 @@ try {
|
||||
'icon' => $bridge->getIcon()
|
||||
);
|
||||
} catch(Error $e) {
|
||||
error_log($e);
|
||||
|
||||
$item = array();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
@@ -274,18 +260,22 @@ try {
|
||||
$item['title'] = 'Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')';
|
||||
}
|
||||
|
||||
$item['uri'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
|
||||
$item['uri'] = (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||
. '?' . http_build_query($params);
|
||||
$item['timestamp'] = time();
|
||||
$item['content'] = buildBridgeException($e, $bridge);
|
||||
|
||||
$items[] = $item;
|
||||
} catch(Exception $e) {
|
||||
error_log($e);
|
||||
|
||||
$item = array();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
$params['_error_time'] = urlencode((int)(time() / 86400));
|
||||
|
||||
$item['uri'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
|
||||
$item['uri'] = (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||
. '?' . http_build_query($params);
|
||||
$item['title'] = 'Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')';
|
||||
$item['timestamp'] = time();
|
||||
$item['content'] = buildBridgeException($e, $bridge);
|
||||
@@ -306,24 +296,22 @@ 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);
|
||||
echo BridgeList::create($showInactive);
|
||||
}
|
||||
} catch(HttpException $e) {
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/plain');
|
||||
die($e->getMessage());
|
||||
} catch(\Exception $e) {
|
||||
error_log($e);
|
||||
header('Content-Type: text/plain', true, $e->getCode());
|
||||
die($e->getMessage());
|
||||
}
|
||||
|
@@ -1,12 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* Authentication module for RSS-Bridge.
|
||||
*
|
||||
* This class implements an authentication module for RSS-Bridge, utilizing the
|
||||
* HTTP authentication capabilities of PHP.
|
||||
*
|
||||
* _Notice_: Authentication via HTTP does not prevent users from accessing files
|
||||
* on your server. If your server supports `.htaccess`, you should globally restrict
|
||||
* access to files instead.
|
||||
*
|
||||
* @link https://php.net/manual/en/features.http-auth.php HTTP authentication with PHP
|
||||
* @link https://httpd.apache.org/docs/2.4/howto/htaccess.html Apache HTTP Server
|
||||
* Tutorial: .htaccess files
|
||||
*
|
||||
* @todo Configuration parameters should be stored internally instead of accessing
|
||||
* the configuration class directly.
|
||||
* @todo Add functions to detect if a user is authenticated or not. This can be
|
||||
* utilized for limiting access to authorized users only.
|
||||
*/
|
||||
class Authentication {
|
||||
|
||||
/**
|
||||
* Throw an exception when trying to create a new instance of this class.
|
||||
* Use {@see Authentication::showPromptIfNeeded()} instead!
|
||||
*
|
||||
* @throws \LogicException if called.
|
||||
*/
|
||||
public function __construct(){
|
||||
throw new \LogicException('Use ' . __CLASS__ . '::showPromptIfNeeded()!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the user for login credentials if necessary.
|
||||
*
|
||||
* Responds to an authentication request or returns the `WWW-Authenticate`
|
||||
* header if authentication is enabled in the configuration of RSS-Bridge
|
||||
* (`[authentication] enable = true`).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function showPromptIfNeeded() {
|
||||
|
||||
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 !');
|
||||
}
|
||||
|
||||
@@ -14,6 +63,13 @@ class Authentication {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if an authentication request was received and compares the
|
||||
* provided username and password to the configuration of RSS-Bridge
|
||||
* (`[authentication] username` and `[authentication] password`).
|
||||
*
|
||||
* @return bool True if authentication succeeded.
|
||||
*/
|
||||
public static function verifyPrompt() {
|
||||
|
||||
if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
|
||||
|
310
lib/Bridge.php
310
lib/Bridge.php
@@ -1,88 +1,296 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/BridgeInterface.php');
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* Factory class responsible for creating bridge objects from a given working
|
||||
* directory, limited by a whitelist.
|
||||
*
|
||||
* This class is capable of:
|
||||
* - Locating bridge classes in the specified working directory (see {@see Bridge::$workingDir})
|
||||
* - Filtering bridges based on a whitelist (see {@see Bridge::$whitelist})
|
||||
* - Creating new bridge instances based on the bridge's name (see {@see Bridge::create()})
|
||||
*
|
||||
* The following example illustrates the intended use for this class.
|
||||
*
|
||||
* ```PHP
|
||||
* require_once __DIR__ . '/rssbridge.php';
|
||||
*
|
||||
* // Step 1: Set the working directory
|
||||
* Bridge::setWorkingDir(__DIR__ . '/../bridges/');
|
||||
*
|
||||
* // Step 2: Add bridges to the whitelist
|
||||
* Bridge::setWhitelist(array('GitHubIssue', 'GoogleSearch', 'Facebook', 'Twitter'));
|
||||
*
|
||||
* // Step 3: Create a new instance of a bridge (based on the name)
|
||||
* $bridge = Bridge::create('GitHubIssue');
|
||||
* ```
|
||||
*/
|
||||
class Bridge {
|
||||
|
||||
static protected $dirBridge;
|
||||
/**
|
||||
* Holds a path to the working directory.
|
||||
*
|
||||
* Do not access this property directly!
|
||||
* Use {@see Bridge::setWorkingDir()} and {@see Bridge::getWorkingDir()} instead.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected static $workingDir = null;
|
||||
|
||||
/**
|
||||
* Holds a list of whitelisted bridges.
|
||||
*
|
||||
* Do not access this property directly!
|
||||
* Use {@see Bridge::getWhitelist()} instead.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $whitelist = array();
|
||||
|
||||
/**
|
||||
* Throws an exception when trying to create a new instance of this class.
|
||||
* Use {@see Bridge::create()} to instanciate a new bridge from the working
|
||||
* directory.
|
||||
*
|
||||
* @throws \LogicException if called.
|
||||
*/
|
||||
public function __construct(){
|
||||
throw new \LogicException('Please use ' . __CLASS__ . '::create for new object.');
|
||||
throw new \LogicException('Use ' . __CLASS__ . '::create($name) to create bridge objects!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new bridge object
|
||||
* @param string $nameBridge Defined bridge name you want use
|
||||
* @return Bridge object dedicated
|
||||
*/
|
||||
static public function create($nameBridge){
|
||||
if(!preg_match('@^[A-Z][a-zA-Z0-9-]*$@', $nameBridge)) {
|
||||
$message = <<<EOD
|
||||
'nameBridge' must start with one uppercase character followed or not by
|
||||
alphanumeric or dash characters!
|
||||
EOD;
|
||||
throw new \InvalidArgumentException($message);
|
||||
* Creates a new bridge object from the working directory.
|
||||
*
|
||||
* @throws \InvalidArgumentException if the requested bridge name is invalid.
|
||||
* @throws \Exception if the requested bridge doesn't exist in the working
|
||||
* directory.
|
||||
* @param string $name Name of the bridge object.
|
||||
* @return object|bool The bridge object or false if the class is not instantiable.
|
||||
*/
|
||||
public static function create($name){
|
||||
if(!self::isBridgeName($name)) {
|
||||
throw new \InvalidArgumentException('Bridge name invalid!');
|
||||
}
|
||||
|
||||
$nameBridge = $nameBridge . 'Bridge';
|
||||
$pathBridge = self::getDir() . $nameBridge . '.php';
|
||||
$name = self::sanitizeBridgeName($name) . 'Bridge';
|
||||
$filePath = self::getWorkingDir() . $name . '.php';
|
||||
|
||||
if(!file_exists($pathBridge)) {
|
||||
throw new \Exception('The bridge you looking for does not exist. It should be at path '
|
||||
. $pathBridge);
|
||||
if(!file_exists($filePath)) {
|
||||
throw new \Exception('Bridge file ' . $filePath . ' does not exist!');
|
||||
}
|
||||
|
||||
require_once $pathBridge;
|
||||
require_once $filePath;
|
||||
|
||||
if((new ReflectionClass($nameBridge))->isInstantiable()) {
|
||||
return new $nameBridge();
|
||||
if((new \ReflectionClass($name))->isInstantiable()) {
|
||||
return new $name();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static public function setDir($dirBridge){
|
||||
if(!is_string($dirBridge)) {
|
||||
throw new \InvalidArgumentException('Dir bridge must be a string.');
|
||||
/**
|
||||
* Sets the working directory.
|
||||
*
|
||||
* @param string $dir Path to the directory containing bridges.
|
||||
* @throws \LogicException if the provided path is not a valid string.
|
||||
* @throws \Exception if the provided path does not exist.
|
||||
* @throws \InvalidArgumentException if $dir is not a directory.
|
||||
* @return void
|
||||
*/
|
||||
public static function setWorkingDir($dir){
|
||||
self::$workingDir = null;
|
||||
|
||||
if(!is_string($dir)) {
|
||||
throw new \InvalidArgumentException('Working directory is not a valid string!');
|
||||
}
|
||||
|
||||
if(!file_exists($dirBridge)) {
|
||||
throw new \Exception('Dir bridge does not exist.');
|
||||
if(!file_exists($dir)) {
|
||||
throw new \Exception('Working directory does not exist!');
|
||||
}
|
||||
|
||||
self::$dirBridge = $dirBridge;
|
||||
}
|
||||
|
||||
static public function getDir(){
|
||||
if(is_null(self::$dirBridge)) {
|
||||
throw new \LogicException(__CLASS__ . ' class need to know bridge path !');
|
||||
if(!is_dir($dir)) {
|
||||
throw new \InvalidArgumentException('Working directory is not a directory!');
|
||||
}
|
||||
|
||||
return self::$dirBridge;
|
||||
self::$workingDir = realpath($dir) . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the available bridges.
|
||||
* @return array List of the bridges
|
||||
*/
|
||||
static public function listBridges(){
|
||||
$listBridge = array();
|
||||
$dirFiles = scandir(self::getDir());
|
||||
* Returns the working directory.
|
||||
* The working directory must be specified with {@see Bridge::setWorkingDir()}!
|
||||
*
|
||||
* @throws \LogicException if the working directory is not set.
|
||||
* @return string The current working directory.
|
||||
*/
|
||||
public static function getWorkingDir(){
|
||||
if(is_null(self::$workingDir)) {
|
||||
throw new \LogicException('Working directory is not set!');
|
||||
}
|
||||
|
||||
if($dirFiles !== false) {
|
||||
foreach($dirFiles as $fileName) {
|
||||
if(preg_match('@^([^.]+)Bridge\.php$@U', $fileName, $out)) {
|
||||
$listBridge[] = $out[1];
|
||||
return self::$workingDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided name is a valid bridge name.
|
||||
*
|
||||
* A valid bridge name starts with a capital letter ([A-Z]), followed by
|
||||
* zero or more alphanumeric characters or hyphen ([A-Za-z0-9-]).
|
||||
*
|
||||
* @param string $name The bridge name.
|
||||
* @return bool true if the name is a valid bridge name, false otherwise.
|
||||
*/
|
||||
public static function isBridgeName($name){
|
||||
return is_string($name) && preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of bridge names from the working directory.
|
||||
*
|
||||
* The list is cached internally to allow for successive calls.
|
||||
*
|
||||
* @return array List of bridge names
|
||||
*/
|
||||
public static function getBridgeNames(){
|
||||
|
||||
static $bridgeNames = array(); // Initialized on first call
|
||||
|
||||
if(empty($bridgeNames)) {
|
||||
$files = scandir(self::getWorkingDir());
|
||||
|
||||
if($files !== false) {
|
||||
foreach($files as $file) {
|
||||
if(preg_match('/^([^.]+)Bridge\.php$/U', $file, $out)) {
|
||||
$bridgeNames[] = $out[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $listBridge;
|
||||
return $bridgeNames;
|
||||
}
|
||||
|
||||
static public function isWhitelisted($whitelist, $name){
|
||||
return in_array($name, $whitelist)
|
||||
|| in_array($name . '.php', $whitelist)
|
||||
|| in_array($name . 'bridge', $whitelist) // DEPRECATED
|
||||
|| in_array($name . 'bridge.php', $whitelist) // DEPRECATED
|
||||
|| (count($whitelist) === 1 && trim($whitelist[0]) === '*');
|
||||
/**
|
||||
* Checks if a bridge is whitelisted.
|
||||
*
|
||||
* @param string $name Name of the bridge.
|
||||
* @return bool True if the bridge is whitelisted.
|
||||
*/
|
||||
public static function isWhitelisted($name){
|
||||
return in_array(self::sanitizeBridgeName($name), self::getWhitelist());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the whitelist.
|
||||
*
|
||||
* On first call this function reads the whitelist from {@see WHITELIST}.
|
||||
* * Each line in the file specifies one bridge on the whitelist.
|
||||
* * An empty file disables all bridges.
|
||||
* * If the file only only contains `*`, all bridges are whitelisted.
|
||||
*
|
||||
* Use {@see Bridge::setWhitelist()} to specify a default whitelist **before**
|
||||
* calling this function! The list is cached internally to allow for
|
||||
* successive calls. If {@see Bridge::setWhitelist()} gets called after this
|
||||
* function, the whitelist is **not** updated again!
|
||||
*
|
||||
* @return array Array of whitelisted bridges
|
||||
*/
|
||||
public static function getWhitelist() {
|
||||
|
||||
static $firstCall = true; // Initialized on first call
|
||||
|
||||
if($firstCall) {
|
||||
|
||||
// Create initial whitelist or load from disk
|
||||
if (!file_exists(WHITELIST) && !empty(self::$whitelist)) {
|
||||
file_put_contents(WHITELIST, implode("\n", self::$whitelist));
|
||||
} else {
|
||||
|
||||
$contents = trim(file_get_contents(WHITELIST));
|
||||
|
||||
if($contents === '*') { // Whitelist all bridges
|
||||
self::$whitelist = self::getBridgeNames();
|
||||
} else {
|
||||
self::$whitelist = array_map('self::sanitizeBridgeName', explode("\n", $contents));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return self::$whitelist;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (default) whitelist.
|
||||
*
|
||||
* If this function is called **before** {@see Bridge::getWhitelist()}, the
|
||||
* provided whitelist will be replaced by a custom whitelist specified in
|
||||
* {@see WHITELIST} (if it exists).
|
||||
*
|
||||
* If this function is called **after** {@see Bridge::getWhitelist()}, the
|
||||
* provided whitelist is taken as is (not updated by the custom whitelist
|
||||
* again).
|
||||
*
|
||||
* @param array $default The whitelist as array of bridge names.
|
||||
* @return void
|
||||
*/
|
||||
public static function setWhitelist($default = array()) {
|
||||
self::$whitelist = array_map('self::sanitizeBridgeName', $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sanitized bridge name.
|
||||
*
|
||||
* The bridge name can be specified in various ways:
|
||||
* * The PHP file name (i.e. `GitHubIssueBridge.php`)
|
||||
* * The PHP file name without file extension (i.e. `GitHubIssueBridge`)
|
||||
* * The bridge name (i.e. `GitHubIssue`)
|
||||
*
|
||||
* Casing is ignored (i.e. `GITHUBISSUE` and `githubissue` are the same).
|
||||
*
|
||||
* A bridge file matching the given bridge name must exist in the working
|
||||
* directory!
|
||||
*
|
||||
* @param string $name The bridge name
|
||||
* @return string|null The sanitized bridge name if the provided name is
|
||||
* valid, null otherwise.
|
||||
*/
|
||||
protected static function sanitizeBridgeName($name) {
|
||||
|
||||
if(is_string($name)) {
|
||||
|
||||
// Trim trailing '.php' if exists
|
||||
if(preg_match('/(.+)(?:\.php)/', $name, $matches)) {
|
||||
$name = $matches[1];
|
||||
}
|
||||
|
||||
// Trim trailing 'Bridge' if exists
|
||||
if(preg_match('/(.+)(?:Bridge)/i', $name, $matches)) {
|
||||
$name = $matches[1];
|
||||
}
|
||||
|
||||
// The name is valid if a corresponding bridge file is found on disk
|
||||
if(in_array(strtolower($name), array_map('strtolower', self::getBridgeNames()))) {
|
||||
$index = array_search(strtolower($name), array_map('strtolower', self::getBridgeNames()));
|
||||
return self::getBridgeNames()[$index];
|
||||
}
|
||||
|
||||
Debug::log('Invalid bridge name specified: "' . $name . '"!');
|
||||
|
||||
}
|
||||
|
||||
return null; // Bad parameter
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,32 +1,112 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/BridgeInterface.php');
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* An abstract class for bridges
|
||||
*
|
||||
* This class implements {@see BridgeInterface} with most common functions in
|
||||
* order to reduce code duplication. Bridges should inherit from this class
|
||||
* instead of implementing the interface manually.
|
||||
*
|
||||
* @todo Move constants to the interface (this is supported by PHP)
|
||||
* @todo Change visibility of constants to protected
|
||||
* @todo Return `self` on more functions to allow chaining
|
||||
* @todo Add specification for PARAMETERS ()
|
||||
* @todo Add specification for $items
|
||||
*/
|
||||
abstract class BridgeAbstract implements BridgeInterface {
|
||||
|
||||
/**
|
||||
* Name of the bridge
|
||||
*
|
||||
* Use {@see BridgeAbstract::getName()} to read this parameter
|
||||
*/
|
||||
const NAME = 'Unnamed bridge';
|
||||
const URI = '';
|
||||
const DESCRIPTION = 'No description provided';
|
||||
const MAINTAINER = 'No maintainer';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const PARAMETERS = array();
|
||||
|
||||
protected $items = array();
|
||||
protected $inputs = array();
|
||||
protected $queriedContext = '';
|
||||
|
||||
/**
|
||||
* Return items stored in the bridge
|
||||
* @return mixed
|
||||
*/
|
||||
* URI to the site the bridge is intended to be used for.
|
||||
*
|
||||
* Use {@see BridgeAbstract::getURI()} to read this parameter
|
||||
*/
|
||||
const URI = '';
|
||||
|
||||
/**
|
||||
* A brief description of what the bridge can do
|
||||
*
|
||||
* Use {@see BridgeAbstract::getDescription()} to read this parameter
|
||||
*/
|
||||
const DESCRIPTION = 'No description provided';
|
||||
|
||||
/**
|
||||
* The name of the maintainer. Multiple maintainers can be separated by comma
|
||||
*
|
||||
* Use {@see BridgeAbstract::getMaintainer()} to read this parameter
|
||||
*/
|
||||
const MAINTAINER = 'No maintainer';
|
||||
|
||||
/**
|
||||
* The default cache timeout for the bridge
|
||||
*
|
||||
* Use {@see BridgeAbstract::getCacheTimeout()} to read this parameter
|
||||
*/
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
/**
|
||||
* Parameters for the bridge
|
||||
*
|
||||
* Use {@see BridgeAbstract::getParameters()} to read this parameter
|
||||
*/
|
||||
const PARAMETERS = array();
|
||||
|
||||
/**
|
||||
* Holds the list of items collected by the bridge
|
||||
*
|
||||
* Items must be collected by {@see BridgeInterface::collectData()}
|
||||
*
|
||||
* Use {@see BridgeAbstract::getItems()} to access items.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $items = array();
|
||||
|
||||
/**
|
||||
* Holds the list of input parameters used by the bridge
|
||||
*
|
||||
* Do not access this parameter directly!
|
||||
* Use {@see BridgeAbstract::setInputs()} and {@see BridgeAbstract::getInput()} instead!
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $inputs = array();
|
||||
|
||||
/**
|
||||
* Holds the name of the queried context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $queriedContext = '';
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getItems(){
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input values for a given context. Existing values are
|
||||
* overwritten.
|
||||
* Sets the input values for a given context.
|
||||
*
|
||||
* @param array $inputs Associative array of inputs
|
||||
* @param string $context The context name
|
||||
* @param string $queriedContext The context name
|
||||
* @return void
|
||||
*/
|
||||
protected function setInputs(array $inputs, $queriedContext){
|
||||
// Import and assign all inputs to their context
|
||||
@@ -103,9 +183,15 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Defined datas with parameters depending choose bridge
|
||||
* @param array array with expected bridge paramters
|
||||
*/
|
||||
* Set inputs for the bridge
|
||||
*
|
||||
* Returns errors and aborts execution if the provided input parameters are
|
||||
* invalid.
|
||||
*
|
||||
* @param array List of input parameters. Each element in this list must
|
||||
* relate to an item in {@see BridgeAbstract::PARAMETERS}
|
||||
* @return void
|
||||
*/
|
||||
public function setDatas(array $inputs){
|
||||
|
||||
if(empty(static::PARAMETERS)) {
|
||||
@@ -148,7 +234,7 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
* Returns the value for the provided input
|
||||
*
|
||||
* @param string $input The input name
|
||||
* @return mixed Returns the input value or null if the input is not defined
|
||||
* @return mixed|null The input value or null if the input is not defined
|
||||
*/
|
||||
protected function getInput($input){
|
||||
if(!isset($this->inputs[$this->queriedContext][$input]['value'])) {
|
||||
@@ -157,32 +243,52 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
return $this->inputs[$this->queriedContext][$input]['value'];
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getDescription(){
|
||||
return static::DESCRIPTION;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getMaintainer(){
|
||||
return static::MAINTAINER;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getName(){
|
||||
return static::NAME;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getIcon(){
|
||||
return '';
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getParameters(){
|
||||
return static::PARAMETERS;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getURI(){
|
||||
return static::URI;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getCacheTimeout(){
|
||||
return static::CACHE_TIMEOUT;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function detectParameters($url){
|
||||
$regex = '/^(https?:\/\/)?(www\.)?(.+?)(\/)?$/';
|
||||
if(empty(static::PARAMETERS)
|
||||
&& preg_match($regex, $url, $urlMatches) > 0
|
||||
&& preg_match($regex, static::URI, $bridgeUriMatches) > 0
|
||||
&& $urlMatches[3] === $bridgeUriMatches[3]) {
|
||||
return array();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* A generator class for a single bridge card on the home page of RSS-Bridge.
|
||||
*
|
||||
* This class generates the HTML content for a single bridge card for the home
|
||||
* page of RSS-Bridge.
|
||||
*
|
||||
* @todo Return error if a caller creates an object of this class.
|
||||
*/
|
||||
final class BridgeCard {
|
||||
|
||||
/**
|
||||
* Build a HTML document string of buttons for each of the provided formats
|
||||
*
|
||||
* @param array $formats A list of format names
|
||||
* @return string The document string
|
||||
*/
|
||||
private static function buildFormatButtons($formats) {
|
||||
$buttons = '';
|
||||
|
||||
@@ -16,6 +42,13 @@ final class BridgeCard {
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form header for a bridge card
|
||||
*
|
||||
* @param string $bridgeName The bridge name
|
||||
* @param bool $isHttps If disabled, adds a warning to the form
|
||||
* @return string The form header
|
||||
*/
|
||||
private static function getFormHeader($bridgeName, $isHttps = false) {
|
||||
$form = <<<EOD
|
||||
<form method="GET" action="?">
|
||||
@@ -31,13 +64,24 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form body for a bridge
|
||||
*
|
||||
* @param string $bridgeName The bridge name
|
||||
* @param array $formats A list of supported formats
|
||||
* @param bool $isActive Indicates if a bridge is enabled or not
|
||||
* @param bool $isHttps Indicates if a bridge uses HTTPS or not
|
||||
* @param string $parameterName Sets the bridge context for the current form
|
||||
* @param array $parameters The bridge parameters
|
||||
* @return string The form body
|
||||
*/
|
||||
private static function getForm($bridgeName,
|
||||
$formats,
|
||||
$isActive = false,
|
||||
$isHttps = false,
|
||||
$parameterName = '',
|
||||
$parameters = array()) {
|
||||
$form = BridgeCard::getFormHeader($bridgeName, $isHttps);
|
||||
$form = self::getFormHeader($bridgeName, $isHttps);
|
||||
|
||||
if(count($parameters) > 0) {
|
||||
|
||||
@@ -65,13 +109,13 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
. PHP_EOL;
|
||||
|
||||
if(!isset($inputEntry['type']) || $inputEntry['type'] === 'text') {
|
||||
$form .= BridgeCard::getTextInput($inputEntry, $idArg, $id);
|
||||
$form .= self::getTextInput($inputEntry, $idArg, $id);
|
||||
} elseif($inputEntry['type'] === 'number') {
|
||||
$form .= BridgeCard::getNumberInput($inputEntry, $idArg, $id);
|
||||
$form .= self::getNumberInput($inputEntry, $idArg, $id);
|
||||
} else if($inputEntry['type'] === 'list') {
|
||||
$form .= BridgeCard::getListInput($inputEntry, $idArg, $id);
|
||||
$form .= self::getListInput($inputEntry, $idArg, $id);
|
||||
} elseif($inputEntry['type'] === 'checkbox') {
|
||||
$form .= BridgeCard::getCheckboxInput($inputEntry, $idArg, $id);
|
||||
$form .= self::getCheckboxInput($inputEntry, $idArg, $id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +124,7 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
}
|
||||
|
||||
if($isActive) {
|
||||
$form .= BridgeCard::buildFormatButtons($formats);
|
||||
$form .= self::buildFormatButtons($formats);
|
||||
} else {
|
||||
$form .= '<span style="font-weight: bold;">Inactive</span>';
|
||||
}
|
||||
@@ -88,6 +132,12 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
return $form . '</form>' . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get input field attributes
|
||||
*
|
||||
* @param array $entry The current entry
|
||||
* @return string The input field attributes
|
||||
*/
|
||||
private static function getInputAttributes($entry) {
|
||||
$retVal = '';
|
||||
|
||||
@@ -103,9 +153,17 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get text input
|
||||
*
|
||||
* @param array $entry The current entry
|
||||
* @param string $id The field ID
|
||||
* @param string $name The field name
|
||||
* @return string The text input field
|
||||
*/
|
||||
private static function getTextInput($entry, $id, $name) {
|
||||
return '<input '
|
||||
. BridgeCard::getInputAttributes($entry)
|
||||
. self::getInputAttributes($entry)
|
||||
. ' id="'
|
||||
. $id
|
||||
. '" type="text" value="'
|
||||
@@ -118,9 +176,17 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
. PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number input
|
||||
*
|
||||
* @param array $entry The current entry
|
||||
* @param string $id The field ID
|
||||
* @param string $name The field name
|
||||
* @return string The number input field
|
||||
*/
|
||||
private static function getNumberInput($entry, $id, $name) {
|
||||
return '<input '
|
||||
. BridgeCard::getInputAttributes($entry)
|
||||
. self::getInputAttributes($entry)
|
||||
. ' id="'
|
||||
. $id
|
||||
. '" type="number" value="'
|
||||
@@ -133,9 +199,17 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
. PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list input
|
||||
*
|
||||
* @param array $entry The current entry
|
||||
* @param string $id The field ID
|
||||
* @param string $name The field name
|
||||
* @return string The list input field
|
||||
*/
|
||||
private static function getListInput($entry, $id, $name) {
|
||||
$list = '<select '
|
||||
. BridgeCard::getInputAttributes($entry)
|
||||
. self::getInputAttributes($entry)
|
||||
. ' id="'
|
||||
. $id
|
||||
. '" name="'
|
||||
@@ -185,19 +259,35 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get checkbox input
|
||||
*
|
||||
* @param array $entry The current entry
|
||||
* @param string $id The field ID
|
||||
* @param string $name The field name
|
||||
* @return string The checkbox input field
|
||||
*/
|
||||
private static function getCheckboxInput($entry, $id, $name) {
|
||||
return '<input '
|
||||
. BridgeCard::getInputAttributes($entry)
|
||||
. self::getInputAttributes($entry)
|
||||
. ' id="'
|
||||
. $id
|
||||
. '" type="checkbox" name="'
|
||||
. $name
|
||||
. '" '
|
||||
. ($entry['defaultValue'] === 'checked' ?: '')
|
||||
. ($entry['defaultValue'] === 'checked' ? 'checked' : '')
|
||||
. ' />'
|
||||
. PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single bridge card
|
||||
*
|
||||
* @param string $bridgeName The bridge name
|
||||
* @param array $formats A list of formats
|
||||
* @param bool $isActive Indicates if the bridge is active or not
|
||||
* @return string The bridge card
|
||||
*/
|
||||
static function displayBridgeCard($bridgeName, $formats, $isActive = true){
|
||||
|
||||
$bridge = Bridge::create($bridgeName);
|
||||
@@ -240,7 +330,7 @@ CARD;
|
||||
if(count($parameters) === 0
|
||||
|| count($parameters) === 1 && array_key_exists('global', $parameters)) {
|
||||
|
||||
$card .= BridgeCard::getForm($bridgeName, $formats, $isActive, $isHttps);
|
||||
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
|
||||
|
||||
} else {
|
||||
|
||||
@@ -254,7 +344,7 @@ CARD;
|
||||
if(!is_numeric($parameterName))
|
||||
$card .= '<h5>' . $parameterName . '</h5>' . PHP_EOL;
|
||||
|
||||
$card .= BridgeCard::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter);
|
||||
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,4 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* The bridge interface
|
||||
*
|
||||
* A bridge is a class that is responsible for collecting and transforming data
|
||||
* from one hosting provider into an internal representation of feed data, that
|
||||
* can later be transformed into different feed formats (see {@see FormatInterface}).
|
||||
*
|
||||
* For this purpose, all bridges need to perform three common operations:
|
||||
*
|
||||
* 1. Collect data from a remote site.
|
||||
* 2. Extract the required contents.
|
||||
* 3. Add the contents to the internal data structure.
|
||||
*
|
||||
* Bridges can optionally specify parameters to customize bridge behavior based
|
||||
* on user input. For example, a user could specify how many items to return in
|
||||
* the feed and where to get them.
|
||||
*
|
||||
* In order to present a bridge on the home page, and for the purpose of bridge
|
||||
* specific behaviour, additional information must be provided by the bridge:
|
||||
*
|
||||
* * **Name**
|
||||
* The name of the bridge that can be displayed to users.
|
||||
*
|
||||
* * **Description**
|
||||
* A brief description for the bridge that can be displayed to users.
|
||||
*
|
||||
* * **URI**
|
||||
* A link to the hosting provider.
|
||||
*
|
||||
* * **Maintainer**
|
||||
* The GitHub username of the bridge maintainer
|
||||
*
|
||||
* * **Parameters**
|
||||
* A list of parameters for customization
|
||||
*
|
||||
* * **Icon**
|
||||
* A link to the favicon of the hosting provider
|
||||
*
|
||||
* * **Cache timeout**
|
||||
* The default cache timeout for the bridge.
|
||||
*/
|
||||
interface BridgeInterface {
|
||||
|
||||
/**
|
||||
@@ -61,4 +114,12 @@ interface BridgeInterface {
|
||||
* @return int Cache timeout
|
||||
*/
|
||||
public function getCacheTimeout();
|
||||
|
||||
/**
|
||||
* Returns parameters from given URL or null if URL is not applicable
|
||||
*
|
||||
* @param string $url URL to extract parameters from
|
||||
* @return array|null List of bridge parameters or null if detection failed.
|
||||
*/
|
||||
public function detectParameters($url);
|
||||
}
|
||||
|
@@ -1,6 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* A generator class for the home page of RSS-Bridge.
|
||||
*
|
||||
* This class generates the HTML content for displaying all bridges on the home
|
||||
* page of RSS-Bridge.
|
||||
*
|
||||
* @todo Return error if a caller creates an object of this class.
|
||||
*/
|
||||
final class BridgeList {
|
||||
|
||||
/**
|
||||
* Get the document head
|
||||
*
|
||||
* @return string The document head
|
||||
*/
|
||||
private static function getHead() {
|
||||
return <<<EOD
|
||||
<head>
|
||||
@@ -22,20 +47,29 @@ final class BridgeList {
|
||||
EOD;
|
||||
}
|
||||
|
||||
private static function getBridges($whitelist, $showInactive, &$totalBridges, &$totalActiveBridges) {
|
||||
/**
|
||||
* Get the document body for all bridge cards
|
||||
*
|
||||
* @param bool $showInactive Inactive bridges are visible on the home page if
|
||||
* enabled.
|
||||
* @param int $totalBridges (ref) Returns the total number of bridges.
|
||||
* @param int $totalActiveBridges (ref) Returns the number of active bridges.
|
||||
* @return string The document body for all bridge cards.
|
||||
*/
|
||||
private static function getBridges($showInactive, &$totalBridges, &$totalActiveBridges) {
|
||||
|
||||
$body = '';
|
||||
$totalActiveBridges = 0;
|
||||
$inactiveBridges = '';
|
||||
|
||||
$bridgeList = Bridge::listBridges();
|
||||
$formats = Format::searchInformation();
|
||||
$bridgeList = Bridge::getBridgeNames();
|
||||
$formats = Format::getFormatNames();
|
||||
|
||||
$totalBridges = count($bridgeList);
|
||||
|
||||
foreach($bridgeList as $bridgeName) {
|
||||
|
||||
if(Bridge::isWhitelisted($whitelist, strtolower($bridgeName))) {
|
||||
if(Bridge::isWhitelisted($bridgeName)) {
|
||||
|
||||
$body .= BridgeCard::displayBridgeCard($bridgeName, $formats);
|
||||
$totalActiveBridges++;
|
||||
@@ -54,19 +88,24 @@ EOD;
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the document header
|
||||
*
|
||||
* @return string The document header
|
||||
*/
|
||||
private static function getHeader() {
|
||||
$warning = '';
|
||||
|
||||
if(defined('DEBUG') && DEBUG === true) {
|
||||
if(defined('DEBUG_INSECURE') && DEBUG_INSECURE === true) {
|
||||
if(Debug::isEnabled()) {
|
||||
if(!Debug::isSecure()) {
|
||||
$warning .= <<<EOD
|
||||
<section class="critical-warning">Warning : Debug mode is active from any location,
|
||||
make sure only you can access RSS-Bridge.</section>
|
||||
<section class="critical-warning">Warning : Debug mode is active from any location,
|
||||
make sure only you can access RSS-Bridge.</section>
|
||||
EOD;
|
||||
} else {
|
||||
$warning .= <<<EOD
|
||||
<section class="warning">Warning : Debug mode is active from your IP address,
|
||||
your requests will bypass the cache.</section>
|
||||
<section class="warning">Warning : Debug mode is active from your IP address,
|
||||
your requests will bypass the cache.</section>
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
@@ -80,6 +119,11 @@ EOD;
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the searchbar
|
||||
*
|
||||
* @return string The searchbar
|
||||
*/
|
||||
private static function getSearchbar() {
|
||||
$query = filter_input(INPUT_GET, 'q');
|
||||
|
||||
@@ -93,9 +137,31 @@ EOD;
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the document footer
|
||||
*
|
||||
* @param int $totalBridges The total number of bridges, shown in the footer
|
||||
* @param int $totalActiveBridges The total number of active bridges, shown
|
||||
* in the footer.
|
||||
* @param bool $showInactive Sets the 'Show active'/'Show inactive' text in
|
||||
* the footer.
|
||||
* @return string The document footer
|
||||
*/
|
||||
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,11 +180,19 @@ EOD;
|
||||
<p class="version">{$version}</p>
|
||||
{$totalActiveBridges}/{$totalBridges} active bridges.<br>
|
||||
{$inactive}
|
||||
{$admininfo}
|
||||
</section>
|
||||
EOD;
|
||||
}
|
||||
|
||||
static function create($whitelist, $showInactive = true) {
|
||||
/**
|
||||
* Create the entire home page
|
||||
*
|
||||
* @param bool $showInactive Inactive bridges are displayed on the home page,
|
||||
* if enabled.
|
||||
* @return string The home page
|
||||
*/
|
||||
static function create($showInactive = true) {
|
||||
|
||||
$totalBridges = 0;
|
||||
$totalActiveBridges = 0;
|
||||
@@ -128,7 +202,7 @@ EOD;
|
||||
. '<body onload="search()">'
|
||||
. BridgeList::getHeader()
|
||||
. BridgeList::getSearchbar()
|
||||
. BridgeList::getBridges($whitelist, $showInactive, $totalBridges, $totalActiveBridges)
|
||||
. BridgeList::getBridges($showInactive, $totalBridges, $totalActiveBridges)
|
||||
. BridgeList::getFooter($totalBridges, $totalActiveBridges, $showInactive)
|
||||
. '</body></html>';
|
||||
|
||||
|
139
lib/Cache.php
139
lib/Cache.php
@@ -1,53 +1,140 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/CacheInterface.php');
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* Factory class responsible for creating cache objects from a given working
|
||||
* directory.
|
||||
*
|
||||
* This class is capable of:
|
||||
* - Locating cache classes in the specified working directory (see {@see Cache::$workingDir})
|
||||
* - Creating new cache instances based on the cache's name (see {@see Cache::create()})
|
||||
*
|
||||
* The following example illustrates the intended use for this class.
|
||||
*
|
||||
* ```PHP
|
||||
* require_once __DIR__ . '/rssbridge.php';
|
||||
*
|
||||
* // Step 1: Set the working directory
|
||||
* Cache::setWorkingDir(__DIR__ . '/../caches/');
|
||||
*
|
||||
* // Step 2: Create a new instance of a cache object (based on the name)
|
||||
* $cache = Cache::create('FileCache');
|
||||
* ```
|
||||
*/
|
||||
class Cache {
|
||||
|
||||
static protected $dirCache;
|
||||
/**
|
||||
* Holds a path to the working directory.
|
||||
*
|
||||
* Do not access this property directly!
|
||||
* Use {@see Cache::setWorkingDir()} and {@see Cache::getWorkingDir()} instead.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected static $workingDir = null;
|
||||
|
||||
/**
|
||||
* Throws an exception when trying to create a new instance of this class.
|
||||
* Use {@see Cache::create()} to create a new cache object from the working
|
||||
* directory.
|
||||
*
|
||||
* @throws \LogicException if called.
|
||||
*/
|
||||
public function __construct(){
|
||||
throw new \LogicException('Please use ' . __CLASS__ . '::create for new object.');
|
||||
throw new \LogicException('Use ' . __CLASS__ . '::create($name) to create cache objects!');
|
||||
}
|
||||
|
||||
static public function create($nameCache){
|
||||
if(!static::isValidNameCache($nameCache)) {
|
||||
throw new \InvalidArgumentException('Name cache must be at least one
|
||||
uppercase follow or not by alphanumeric or dash characters.');
|
||||
/**
|
||||
* Creates a new cache object from the working directory.
|
||||
*
|
||||
* @throws \InvalidArgumentException if the requested cache name is invalid.
|
||||
* @throws \Exception if the requested cache file doesn't exist in the
|
||||
* working directory.
|
||||
* @param string $name Name of the cache object.
|
||||
* @return object|bool The cache object or false if the class is not instantiable.
|
||||
*/
|
||||
public static function create($name){
|
||||
if(!self::isCacheName($name)) {
|
||||
throw new \InvalidArgumentException('Cache name invalid!');
|
||||
}
|
||||
|
||||
$pathCache = self::getDir() . $nameCache . '.php';
|
||||
$filePath = self::getWorkingDir() . $name . '.php';
|
||||
|
||||
if(!file_exists($pathCache)) {
|
||||
throw new \Exception('The cache you looking for does not exist.');
|
||||
if(!file_exists($filePath)) {
|
||||
throw new \Exception('Cache file ' . $filePath . ' does not exist!');
|
||||
}
|
||||
|
||||
require_once $pathCache;
|
||||
require_once $filePath;
|
||||
|
||||
return new $nameCache();
|
||||
if((new \ReflectionClass($name))->isInstantiable()) {
|
||||
return new $name();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static public function setDir($dirCache){
|
||||
if(!is_string($dirCache)) {
|
||||
throw new \InvalidArgumentException('Dir cache must be a string.');
|
||||
/**
|
||||
* Sets the working directory.
|
||||
*
|
||||
* @param string $dir Path to a directory containing cache classes
|
||||
* @throws \InvalidArgumentException if $dir is not a string.
|
||||
* @throws \Exception if the working directory doesn't exist.
|
||||
* @throws \InvalidArgumentException if $dir is not a directory.
|
||||
* @return void
|
||||
*/
|
||||
public static function setWorkingDir($dir){
|
||||
self::$workingDir = null;
|
||||
|
||||
if(!is_string($dir)) {
|
||||
throw new \InvalidArgumentException('Working directory is not a valid string!');
|
||||
}
|
||||
|
||||
if(!file_exists($dirCache)) {
|
||||
throw new \Exception('Dir cache does not exist.');
|
||||
if(!file_exists($dir)) {
|
||||
throw new \Exception('Working directory does not exist!');
|
||||
}
|
||||
|
||||
self::$dirCache = $dirCache;
|
||||
if(!is_dir($dir)) {
|
||||
throw new \InvalidArgumentException('Working directory is not a directory!');
|
||||
}
|
||||
|
||||
self::$workingDir = realpath($dir) . '/';
|
||||
}
|
||||
|
||||
static public function getDir(){
|
||||
$dirCache = self::$dirCache;
|
||||
|
||||
if(is_null($dirCache)) {
|
||||
throw new \LogicException(__CLASS__ . ' class need to know cache path !');
|
||||
/**
|
||||
* Returns the working directory.
|
||||
* The working directory must be set with {@see Cache::setWorkingDir()}!
|
||||
*
|
||||
* @throws \LogicException if the working directory is not set.
|
||||
* @return string The current working directory.
|
||||
*/
|
||||
public static function getWorkingDir(){
|
||||
if(is_null(self::$workingDir)) {
|
||||
throw new \LogicException('Working directory is not set!');
|
||||
}
|
||||
|
||||
return $dirCache;
|
||||
return self::$workingDir;
|
||||
}
|
||||
|
||||
static public function isValidNameCache($nameCache){
|
||||
return preg_match('@^[A-Z][a-zA-Z0-9-]*$@', $nameCache);
|
||||
/**
|
||||
* Returns true if the provided name is a valid cache name.
|
||||
*
|
||||
* A valid cache name starts with a capital letter ([A-Z]), followed by
|
||||
* zero or more alphanumeric characters or hyphen ([A-Za-z0-9-]).
|
||||
*
|
||||
* @param string $name The cache name.
|
||||
* @return bool true if the name is a valid cache name, false otherwise.
|
||||
*/
|
||||
public static function isCacheName($name){
|
||||
return is_string($name) && preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name) === 1;
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* The cache interface
|
||||
*
|
||||
* @todo Add missing function to the interface
|
||||
* @todo Explain parameters and return values in more detail
|
||||
* @todo Return self more often (to allow call chaining)
|
||||
*/
|
||||
interface CacheInterface {
|
||||
|
||||
/**
|
||||
* Loads data from cache
|
||||
*
|
||||
* @return mixed The cache data
|
||||
*/
|
||||
public function loadData();
|
||||
|
||||
/**
|
||||
* Stores data to the cache
|
||||
*
|
||||
* @param mixed $datas The data to store
|
||||
* @return self The cache object
|
||||
*/
|
||||
public function saveData($datas);
|
||||
|
||||
/**
|
||||
* Returns the timestamp for the curent cache file
|
||||
*
|
||||
* @return int Timestamp
|
||||
*/
|
||||
public function getTime();
|
||||
|
||||
/**
|
||||
* Removes any data that is older than the specified duration from cache
|
||||
*
|
||||
* @param int $duration The cache duration in seconds
|
||||
*/
|
||||
public function purgeCache($duration);
|
||||
}
|
||||
|
@@ -1,15 +1,86 @@
|
||||
<?php
|
||||
class Configuration {
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
public static $VERSION = '2018-10-15';
|
||||
/**
|
||||
* Configuration module for RSS-Bridge.
|
||||
*
|
||||
* This class implements a configuration module for RSS-Bridge.
|
||||
*/
|
||||
final class Configuration {
|
||||
|
||||
public static $config = null;
|
||||
/**
|
||||
* Holds the current release version of RSS-Bridge.
|
||||
*
|
||||
* Do not access this property directly!
|
||||
* Use {@see Configuration::getVersion()} instead.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @todo Replace this property by a constant.
|
||||
*/
|
||||
public static $VERSION = '2018-12-11';
|
||||
|
||||
/**
|
||||
* Holds the configuration data.
|
||||
*
|
||||
* Do not access this property directly!
|
||||
* Use {@see Configuration::getConfig()} instead.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private static $config = null;
|
||||
|
||||
/**
|
||||
* Throw an exception when trying to create a new instance of this class.
|
||||
*
|
||||
* @throws \LogicException if called.
|
||||
*/
|
||||
public function __construct(){
|
||||
throw new \LogicException('Can\'t create object of this class!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the current installation of RSS-Bridge and PHP.
|
||||
*
|
||||
* Returns an error message and aborts execution if the installation does
|
||||
* not satisfy the requirements of RSS-Bridge.
|
||||
*
|
||||
* **Requirements**
|
||||
* - PHP 5.6.0 or higher
|
||||
* - `openssl` extension
|
||||
* - `libxml` extension
|
||||
* - `mbstring` extension
|
||||
* - `simplexml` extension
|
||||
* - `curl` extension
|
||||
* - `json` extension
|
||||
* - The cache folder specified by {@see PATH_CACHE} requires write permission
|
||||
* - The whitelist file specified by {@see WHITELIST} requires write permission
|
||||
*
|
||||
* @link http://php.net/supported-versions.php PHP Supported Versions
|
||||
* @link http://php.net/manual/en/book.openssl.php OpenSSL
|
||||
* @link http://php.net/manual/en/book.libxml.php libxml
|
||||
* @link http://php.net/manual/en/book.mbstring.php Multibyte String (mbstring)
|
||||
* @link http://php.net/manual/en/book.simplexml.php SimpleXML
|
||||
* @link http://php.net/manual/en/book.curl.php Client URL Library (curl)
|
||||
* @link http://php.net/manual/en/book.json.php JavaScript Object Notation (json)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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,27 +102,53 @@ 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)))
|
||||
die('RSS-Bridge does not have write permissions for ' . WHITELIST_FILE . '!');
|
||||
// Check whitelist file permissions
|
||||
if(!file_exists(WHITELIST) && !is_writable(dirname(WHITELIST)))
|
||||
die('RSS-Bridge does not have write permissions for ' . WHITELIST . '!');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the configuration from disk and checks if the parameters are valid.
|
||||
*
|
||||
* Returns an error message and aborts execution if the configuration is invalid.
|
||||
*
|
||||
* The RSS-Bridge configuration is split into two files:
|
||||
* - `config.default.ini.php`: The default configuration file that ships with
|
||||
* every release of RSS-Bridge (do not modify this file!).
|
||||
* - `config.ini.php`: The local configuration file that can be modified by
|
||||
* server administrators.
|
||||
*
|
||||
* The files must be located at {@see PATH_ROOT}
|
||||
*
|
||||
* RSS-Bridge will first load `config.default.ini.php` into memory and then
|
||||
* replace parameters with the contents of `config.ini.php`. That way new
|
||||
* parameters are automatically initialized with default values and custom
|
||||
* configurations can be reduced to the minimum set of parametes necessary
|
||||
* (only the ones that changed).
|
||||
*
|
||||
* The configuration files must be placed in the root folder of RSS-Bridge
|
||||
* (next to `index.php`).
|
||||
*
|
||||
* _Notice_: The configuration is stored in {@see Configuration::$config}.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function loadConfiguration() {
|
||||
|
||||
if(!file_exists('config.default.ini.php'))
|
||||
if(!file_exists(PATH_ROOT . 'config.default.ini.php'))
|
||||
die('The default configuration file "config.default.ini.php" is missing!');
|
||||
|
||||
Configuration::$config = parse_ini_file('config.default.ini.php', true, INI_SCANNER_TYPED);
|
||||
Configuration::$config = parse_ini_file(PATH_ROOT . 'config.default.ini.php', true, INI_SCANNER_TYPED);
|
||||
if(!Configuration::$config)
|
||||
die('Error parsing config.default.ini.php');
|
||||
|
||||
if(file_exists('config.ini.php')) {
|
||||
if(file_exists(PATH_ROOT . 'config.ini.php')) {
|
||||
// Replace default configuration with custom settings
|
||||
foreach(parse_ini_file('config.ini.php', true, INI_SCANNER_TYPED) as $header => $section) {
|
||||
foreach(parse_ini_file(PATH_ROOT . 'config.ini.php', true, INI_SCANNER_TYPED) as $header => $section) {
|
||||
foreach($section as $key => $value) {
|
||||
// Skip unknown sections and keys
|
||||
if(array_key_exists($header, Configuration::$config) && array_key_exists($key, Configuration::$config[$header])) {
|
||||
@@ -64,22 +161,27 @@ class Configuration {
|
||||
if(!is_string(self::getConfig('proxy', 'url')))
|
||||
die('Parameter [proxy] => "url" is not a valid string! Please check "config.ini.php"!');
|
||||
|
||||
if(!empty(self::getConfig('proxy', 'url')))
|
||||
if(!empty(self::getConfig('proxy', 'url'))) {
|
||||
/** URL of the proxy server */
|
||||
define('PROXY_URL', self::getConfig('proxy', 'url'));
|
||||
}
|
||||
|
||||
if(!is_bool(self::getConfig('proxy', 'by_bridge')))
|
||||
die('Parameter [proxy] => "by_bridge" is not a valid Boolean! Please check "config.ini.php"!');
|
||||
|
||||
/** True if proxy usage can be enabled selectively for each bridge */
|
||||
define('PROXY_BYBRIDGE', self::getConfig('proxy', 'by_bridge'));
|
||||
|
||||
if(!is_string(self::getConfig('proxy', 'name')))
|
||||
die('Parameter [proxy] => "name" is not a valid string! Please check "config.ini.php"!');
|
||||
|
||||
/** Name of the proxy server */
|
||||
define('PROXY_NAME', self::getConfig('proxy', 'name'));
|
||||
|
||||
if(!is_bool(self::getConfig('cache', 'custom_timeout')))
|
||||
die('Parameter [cache] => "custom_timeout" is not a valid Boolean! Please check "config.ini.php"!');
|
||||
|
||||
/** True if the cache timeout can be specified by the user */
|
||||
define('CUSTOM_CACHE_TIMEOUT', self::getConfig('cache', 'custom_timeout'));
|
||||
|
||||
if(!is_bool(self::getConfig('authentication', 'enable')))
|
||||
@@ -91,23 +193,44 @@ class Configuration {
|
||||
if(!is_string(self::getConfig('authentication', 'password')))
|
||||
die('Parameter [authentication] => "password" is not a valid string! Please check "config.ini.php"!');
|
||||
|
||||
if(!empty(self::getConfig('admin', 'email'))
|
||||
&& !filter_var(self::getConfig('admin', 'email'), FILTER_VALIDATE_EMAIL))
|
||||
die('Parameter [admin] => "email" is not a valid email address! Please check "config.ini.php"!');
|
||||
|
||||
}
|
||||
|
||||
public static function getConfig($category, $key) {
|
||||
/**
|
||||
* Returns the value of a parameter identified by section and key.
|
||||
*
|
||||
* @param string $section The section name.
|
||||
* @param string $key The property name (key).
|
||||
* @return mixed|null The parameter value.
|
||||
*/
|
||||
public static function getConfig($section, $key) {
|
||||
|
||||
if(array_key_exists($category, self::$config) && array_key_exists($key, self::$config[$category])) {
|
||||
return self::$config[$category][$key];
|
||||
if(array_key_exists($section, self::$config) && array_key_exists($key, self::$config[$section])) {
|
||||
return self::$config[$section][$key];
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current version string of RSS-Bridge.
|
||||
*
|
||||
* This function returns the contents of {@see Configuration::$VERSION} for
|
||||
* regular installations and the git branch name and commit id for instances
|
||||
* running in a git environment.
|
||||
*
|
||||
* @return string The version string.
|
||||
*/
|
||||
public static function getVersion() {
|
||||
|
||||
$headFile = '.git/HEAD';
|
||||
$headFile = PATH_ROOT . '.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];
|
||||
|
121
lib/Debug.php
Normal file
121
lib/Debug.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements functions for debugging purposes. Debugging can be enabled by
|
||||
* placing a file named DEBUG in {@see PATH_ROOT}.
|
||||
*
|
||||
* The file specifies a whitelist of IP addresses on which debug mode will be
|
||||
* enabled. An empty file enables debug mode for everyone (highly discouraged
|
||||
* for public servers!). Each line in the file specifies one client in the
|
||||
* whitelist. For example:
|
||||
*
|
||||
* * `192.168.1.72`
|
||||
* * `127.0.0.1`
|
||||
* * `::1`
|
||||
*
|
||||
* Notice: If you are running RSS-Bridge on your local machine, you need to add
|
||||
* localhost (either `127.0.0.1` for IPv4 or `::1` for IPv6) to your whitelist!
|
||||
*
|
||||
* Warning: In debug mode your server may display sensitive information! For
|
||||
* security reasons it is recommended to whitelist only specific IP addresses.
|
||||
*/
|
||||
class Debug {
|
||||
|
||||
/**
|
||||
* Indicates if debug mode is enabled.
|
||||
*
|
||||
* Do not access this property directly!
|
||||
* Use {@see Debug::isEnabled()} instead.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $enabled = false;
|
||||
|
||||
/**
|
||||
* Indicates if debug mode is secure.
|
||||
*
|
||||
* Do not access this property directly!
|
||||
* Use {@see Debug::isSecure()} instead.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $secure = false;
|
||||
|
||||
/**
|
||||
* Returns true if debug mode is enabled
|
||||
*
|
||||
* If debug mode is enabled, sets `display_errors = 1` and `error_reporting = E_ALL`
|
||||
*
|
||||
* @return bool True if enabled.
|
||||
*/
|
||||
public static function isEnabled() {
|
||||
static $firstCall = true; // Initialized on first call
|
||||
|
||||
if($firstCall && file_exists(PATH_ROOT . 'DEBUG')) {
|
||||
|
||||
$debug_whitelist = trim(file_get_contents(PATH_ROOT . 'DEBUG'));
|
||||
|
||||
self::$enabled = empty($debug_whitelist) || in_array($_SERVER['REMOTE_ADDR'],
|
||||
explode("\n", str_replace("\r", '', $debug_whitelist)
|
||||
)
|
||||
);
|
||||
|
||||
if(self::$enabled) {
|
||||
ini_set('display_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
self::$secure = !empty($debug_whitelist);
|
||||
}
|
||||
|
||||
$firstCall = false; // Skip check on next call
|
||||
|
||||
}
|
||||
|
||||
return self::$enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if debug mode is enabled only for specific IP addresses.
|
||||
*
|
||||
* Notice: The security flag is set by {@see Debug::isEnabled()}. If this
|
||||
* function is called before {@see Debug::isEnabled()}, the default value is
|
||||
* false!
|
||||
*
|
||||
* @return bool True if debug mode is secure
|
||||
*/
|
||||
public static function isSecure() {
|
||||
return self::$secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a debug message to error_log if debug mode is enabled
|
||||
*
|
||||
* @param string $text The message to add to error_log
|
||||
*/
|
||||
public static function log($text) {
|
||||
if(!self::isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
|
||||
$calling = end($backtrace);
|
||||
$message = $calling['file'] . ':'
|
||||
. $calling['line'] . ' class '
|
||||
. (isset($calling['class']) ? $calling['class'] : '<no-class>') . '->'
|
||||
. $calling['function'] . ' - '
|
||||
. $text;
|
||||
|
||||
error_log($message);
|
||||
}
|
||||
}
|
@@ -1,17 +1,28 @@
|
||||
<?php
|
||||
class HttpException extends \Exception{}
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns an URL that automatically populates a new issue on GitHub based
|
||||
* on the information provided
|
||||
*
|
||||
* @param $title string Sets the title of the issue
|
||||
* @param $body string Sets the body of the issue (GitHub markdown applies)
|
||||
* @param $labels mixed (optional) Specifies labels to add to the issue
|
||||
* @param $maintainer string (optional) Specifies the maintainer for the issue.
|
||||
* @param string $title string Sets the title of the issue
|
||||
* @param string $body string Sets the body of the issue (GitHub markdown applies)
|
||||
* @param string $labels mixed (optional) Specifies labels to add to the issue
|
||||
* @param string $maintainer string (optional) Specifies the maintainer for the issue.
|
||||
* The maintainer only applies if part of the development team!
|
||||
* @return string Returns a qualified URL to a new issue with populated conent.
|
||||
* Returns null if title or body is null or empty
|
||||
* @return string|null A qualified URL to a new issue with populated conent or null.
|
||||
*
|
||||
* @todo This function belongs inside a class
|
||||
*/
|
||||
function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null){
|
||||
if(!isset($title) || !isset($body) || empty($title) || empty($body)) {
|
||||
@@ -19,7 +30,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);
|
||||
@@ -48,10 +60,11 @@ function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null
|
||||
/**
|
||||
* Returns the exception message as HTML string
|
||||
*
|
||||
* @param $e Exception The exception to show
|
||||
* @param $bridge object The bridge object
|
||||
* @return string Returns the exception as HTML string. Returns null if the
|
||||
* provided parameter are invalid
|
||||
* @param object $e Exception The exception to show
|
||||
* @param object $bridge object The bridge object
|
||||
* @return string|null Returns the exception as HTML string or null.
|
||||
*
|
||||
* @todo This function belongs inside a class
|
||||
*/
|
||||
function buildBridgeException($e, $bridge){
|
||||
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
|
||||
@@ -64,7 +77,7 @@ function buildBridgeException($e, $bridge){
|
||||
$body = 'Error message: `'
|
||||
. $e->getMessage()
|
||||
. "`\nQuery string: `"
|
||||
. $_SERVER['QUERY_STRING']
|
||||
. (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '')
|
||||
. "`\nVersion: `"
|
||||
. Configuration::getVersion()
|
||||
. '`';
|
||||
@@ -86,10 +99,11 @@ EOD;
|
||||
/**
|
||||
* Returns the exception message as HTML string
|
||||
*
|
||||
* @param $e Exception The exception to show
|
||||
* @param $bridge object The bridge object
|
||||
* @return string Returns the exception as HTML string. Returns null if the
|
||||
* provided parameter are invalid
|
||||
* @param object $e Exception The exception to show
|
||||
* @param object $bridge object The bridge object
|
||||
* @return string|null Returns the exception as HTML string or null.
|
||||
*
|
||||
* @todo This function belongs inside a class
|
||||
*/
|
||||
function buildTransformException($e, $bridge){
|
||||
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
|
||||
@@ -102,7 +116,8 @@ function buildTransformException($e, $bridge){
|
||||
$body = 'Error message: `'
|
||||
. $e->getMessage()
|
||||
. "`\nQuery string: `"
|
||||
. $_SERVER['QUERY_STRING'] . '`';
|
||||
. (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '')
|
||||
. '`';
|
||||
|
||||
$link = buildGitHubIssueQuery($title, $body, 'bug report', $bridge->getMaintainer());
|
||||
$header = buildHeader($e, $bridge);
|
||||
@@ -113,6 +128,15 @@ function buildTransformException($e, $bridge){
|
||||
return buildPage($title, $header, $section);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new HTML header with data from a exception an a bridge
|
||||
*
|
||||
* @param object $e The exception object
|
||||
* @param object $bridge The bridge object
|
||||
* @return string The HTML header
|
||||
*
|
||||
* @todo This function belongs inside a class
|
||||
*/
|
||||
function buildHeader($e, $bridge){
|
||||
return <<<EOD
|
||||
<header>
|
||||
@@ -123,6 +147,17 @@ function buildHeader($e, $bridge){
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new HTML section
|
||||
*
|
||||
* @param object $e The exception object
|
||||
* @param object $bridge The bridge object
|
||||
* @param string $message The message to display
|
||||
* @param string $link The link to include in the anchor
|
||||
* @return string The HTML section
|
||||
*
|
||||
* @todo This function belongs inside a class
|
||||
*/
|
||||
function buildSection($e, $bridge, $message, $link){
|
||||
return <<<EOD
|
||||
<section>
|
||||
@@ -141,6 +176,16 @@ function buildSection($e, $bridge, $message, $link){
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new HTML page
|
||||
*
|
||||
* @param string $title The HTML title
|
||||
* @param string $header The HTML header
|
||||
* @param string $section The HTML section
|
||||
* @return string The HTML page
|
||||
*
|
||||
* @todo This function belongs inside a class
|
||||
*/
|
||||
function buildPage($title, $header, $section){
|
||||
return <<<EOD
|
||||
<!DOCTYPE html>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user