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

Compare commits

...

76 Commits

Author SHA1 Message Date
logmanoriginal
ae2c35c18a [Configuration] Bump version to 2019-03-17 2019-03-17 20:28:55 +01:00
logmanoriginal
5b80bcaa04 [README] Update list of contributors
The list acutally didn't change, but it's sorted properly now
(thanks to em92 for the suggestion)
2019-03-17 20:28:15 +01:00
fulmeek
5ea985164e [OneFortuneADayBridge] use date in UTC for seed (#1059) 2019-03-14 19:44:36 +01:00
fulmeek
696afa96d3 [BakaUpdatesMangaReleasesBridge] filter title and groups (#1058)
Baka-Updates Manga uses an asterisk (*) to denote series information have
been updated within the last 24 hours. This is not helpful in a feed.
2019-03-14 19:43:00 +01:00
Roliga
326a707739 [SoundcloudBridge] Update API key (#1062) 2019-03-12 13:29:11 +01:00
LogMANOriginal
1ac66b3fdc [README] Add sqlite3 as requirement for SQLiteCache 2019-03-02 19:42:40 +01:00
logmanoriginal
f450b2e118 [SQLiteCache] Check sqlite3 extension in __construct
Checks if the sqlite3 extension is loaded and throws an error
if it's missing.
2019-03-02 19:33:44 +01:00
sysadminstory
688c950916 [DealabsBridge] Patch unparsable Deal date (#1053)
In case of a unparsable date, the text to DateTime object failed, and
this resulted to a Fatal error while using this DateTime object . To
prvent this fatal error, if the date parsing failse, then a DateTime
object is created with the actual date.
2019-03-02 19:10:57 +01:00
fulmeek
9d85b951f7 [BakaUpdatesMangaReleasesBridge] rework to parse new layout (#1052)
* rework to parse new layout
* skip incomplete rows

The last row could have fewer columns if there are less rows than the items limit. This usually should not happen, though.

* use constant for skipping
2019-03-02 19:09:16 +01:00
somini
dac685b887 [ComboiosDePortugalBridge] Add new bridge (#1049) 2019-03-02 19:05:23 +01:00
ORelio
d37f0c14a0 [LeMondeInformatique] Handle special articles (#1039)
Fix content extraction for special article compiling previous articles
2019-03-02 19:03:29 +01:00
Ryan Liptak
b96c25a3af [BandcampBridge] Update to use newer POST API (#1045)
Bandcamp tags pages have a new layout and now use a POST API endpoint to view each page of releases.

Output of this bridge should be almost the same as before, with a few small improvements:
- Small album image in 'content', larger album image in 'enclosures'
- RSS item titles/authors are appended with the releaser in parentheses if the artist name and the releaser are different (i.e. Record Label's Bandcamp releases an album called Bar by the band named Foo, it would get the title 'Foo - Bar (Record Label)' and the author 'Foo (Record Label)')
2019-02-24 12:08:34 +01:00
fulmeek
dc1b1b13cc [SQLiteCache] Implement cache based on SQLite 3 (#1035) 2019-02-24 12:04:27 +01:00
logmanoriginal
e3588f62bd [Cache] Fix cache types ending on 'cache' are not detected correctly
References #1000
2019-02-24 11:56:43 +01:00
fulmeek
958ba815c7 [OneFortuneADayBridge] Add lucky number feature (#1038) 2019-02-24 11:49:17 +01:00
somini
3d24596a52 [AsahiShimbunAJW] Add new Bridge (#1036) 2019-02-24 11:47:29 +01:00
Lyra
f9ed934c8c Update contributors and bump version 2019-02-19 22:05:06 +01:00
Nono
777c204838 [VMwareSecurityBridge] New Bridge (#1041)
* Create VMwareSecurityBridge.php
2019-02-19 21:53:20 +01:00
Nono
ae40f7b388 [MozillaSecurityBridge] Make the URI unique by adding timestamp (#1005)
* added unique UID + URI 

if UID is mandatory for RSS-Bridge, the unicity of the URI is also mandatory for some reader (like kriss feed).
2019-02-19 21:50:00 +01:00
Lyra
473a62ed44 [RoadAndTrackBridge] Added new bridge 2019-02-12 15:12:04 +01:00
Klimplant
4c58768d4d [CachetBridge] Add new bridge (#1034)
* Fix issue with CachetAPI Pagination

Fixing issue that only the oldest 20 entries were shown.

_Background:_

_Cachet has a, lets call it odd, system of pagination. On the first page you see the incidents first created, so they are not what you want to see. But on the last page you can have 1 or 20 of the newest incidents. So you have to take the incidents from the last page (call it Pmax) and combine them with the incidents from  Pmax - 1._
2019-02-11 21:07:46 +01:00
ORelio
ca9c2abb60 [FeedExpander] Fix item href being used as feed uri (#1033) 2019-02-11 19:07:03 +01:00
logmanoriginal
556a417dd6 core: Add support for custom cache types via config.ini.php
This commit adds support for a new parameter which specifies the type
of cache to use for caching. It is specified in config.ini.php:

 [cache]

 type = "..."

Currently only one type of cache is supported (see /caches). All uses
of 'FileCache' were replaced by this configuration option.

Note: Caching currently depends on files and folders (due to FileCache).
Experience may vary depending on the selected cache type. For now always
check if FileCache is working before testing alternative types.

References #1000
2019-02-06 18:52:44 +01:00
LogMANOriginal
51ee541d5a core: Implement action factory (#1002) 2019-02-06 18:34:51 +01:00
Nova
69cb65c1af [GlowficBridge] Add new bridge (#1031) 2019-02-06 18:20:25 +01:00
David Pedersen
29b187fc12 [AppleMusicBridge] Add new bridge (#1026) 2019-02-06 17:43:20 +01:00
fulmeek
80f6a8b3d4 [MrssFormat] Rework to make it valid RSS 2.0 + Media RSS (#996) 2019-02-06 17:18:33 +01:00
logmanoriginal
32d4da8b76 [Bridge] Fix failed to open stream when reading non-existing whitelist 2019-02-04 17:35:40 +01:00
fulmeek
0063d2c376 [HtmlFormat] minor typographical fix-ups (#1009) 2019-02-04 15:33:13 +01:00
fulmeek
11a39af35c [FormatImplementationTest] Add unit tests for format implementations (#1008) 2019-02-04 14:59:09 +01:00
fulmeek
f65a4076ba [CacheImplementationTest] Add unit tests for cache implementations (#1007) 2019-02-04 14:58:11 +01:00
triatic
25593d9c18 [TwitterBridge] Append username of retweeter to author (#1016)
Append username of retweeter to author. Useful when viewing all unread tweets in an RSS reader which are not sorted within username folders.
2019-02-04 14:56:07 +01:00
LogMANOriginal
394149b114 core: Add item uid (#1017)
'uid' represents the unique id for a feed item. This item is null by
default and can be set to any string value. The provided string value
is always hashed to sha1 to make it the same length in all cases.

References #977, #1005
2019-02-03 20:56:41 +01:00
logmanoriginal
a29512deee [BridgeCard] Don't warn about the 'required' attribute if it is set to false 2019-01-22 19:12:37 +01:00
logmanoriginal
e0db349a57 bridges: Fix bridges that don't pass the unit test 2019-01-22 18:24:32 +01:00
logmanoriginal
d532d0e0c4 [BridgeImplementationTest] Add test for "required" attribute on lists and checkboxes
Lists and checkboxes don't support the "required" flag and should not
define it. Note that the "required" flag can be set to false if so
desired.
2019-01-22 18:22:49 +01:00
logmanoriginal
434c12672f lib: Ignore required attribute on lists an checkboxes
References #1014
2019-01-22 18:11:52 +01:00
fulmeek
ab2e566ee1 [AtomFormat] Update to comply with RFC 4287 (#995)
https://tools.ietf.org/html/rfc4287
2019-01-21 17:22:30 +01:00
fulmeek
493e76e4b9 [BakaUpdatesMangaReleasesBridge] Add new bridge (#999) 2019-01-15 16:36:42 +01:00
logmanoriginal
37d882a8d5 [GlassdoorBridge] Fix incorrect CSS selector 2019-01-13 22:04:21 +01:00
logmanoriginal
bcd7bccc46 vendor: Update PHP Simple HTML DOM Parser to 1.8.1
https://sourceforge.net/projects/simplehtmldom/files/simplehtmldom/1.8.1/

Note: Some bridges may need fixes in their CSS queries if they don't follow
the specification.
2019-01-13 22:02:59 +01:00
logmanoriginal
2def7a04a3 Bump version to dev.2019-01-13 2019-01-13 19:23:59 +01:00
logmanoriginal
3c5b23daa6 [README] Update list of contributors 2019-01-13 19:18:40 +01:00
logmanoriginal
ef6709c402 Bump version to 2019-01-13 2019-01-13 19:15:06 +01:00
Quentin de Longraye
fc96e97d51 [N26Bridge] Add new bridge (#1006)
https://n26.com
2019-01-13 19:12:31 +01:00
fulmeek
600f2290b6 [BridgeImplementationTest] Refactor unit test to check bridges (#980) 2019-01-08 20:02:51 +01:00
triatic
245af35a60 [contents] improve file_get_contents() reporting (#986)
Suppress any errors from file_get_contents() and include the PHP error in the feed instead.
2019-01-06 20:30:02 +01:00
Corentin Garcia
ef4923ae5c [AmazonBridge] Fix parsing of list item (#998)
Closes #993 
Closes #769
2019-01-06 18:38:53 +01:00
Corentin Garcia
18229b5c70 [InstagramBridge] Add author if available in response (#997)
Closes #905
2019-01-06 18:14:23 +01:00
logmanoriginal
3160e62293 [DiscogsBridge] Fix timestamp parsing
References #978
2019-01-05 15:24:44 +01:00
Roliga
f81d1b0846 [TrelloBridge] Fix actions with missing image urls (#987)
When an action is added then removed the image url properties of that
action are missing
2019-01-05 13:27:12 +01:00
fulmeek
8801ac9e64 format: Refactor JsonFormat to JSON Feed version 1 (#988)
JsonFormat now implements https://jsonfeed.org/version/1

Closes #618
2019-01-05 13:20:11 +01:00
fulmeek
288d4de218 bridges: Fix bridges to pass unit test (#984)
* [DealabsBridge] fixed parameters
* [DemonoidBridge] added parameter context names
* [DevToBridge] fixed parameters
* [ExtremeDownloadBridge] fixed parameters
* [GithubIssueBridge] fixed parameters
* [InstagramBridge] added parameter context names
* [MydealsBridge] fixed parameters
* [OnVaSortirBridge] fixed parameters
* [ThingyverseBridge] fixed parameters
* [HotUKDealsBridge] fixed parameters
* [FeedExpanderExample] added proper URI
* [GQMagazineBridge] fixed parameters and getDomain()
* [MozillaSecurityBridge] fixed filename

References #980
2019-01-05 12:29:26 +01:00
Corentin Garcia
f3f33cabed [EliteDangerousGalnetBridge] Add support for others website languages (#992)
* [EliteDangerousGalnetBridge] Add support for others website languages

* [EliteDangerousGalnetBridge] Fix post title
2019-01-03 18:29:29 +01:00
triatic
3e45643418 [index] Fix error when no items defined (#983)
Fix PHP Notice:  Undefined offset: 0. Error below triggers when there are no items:

PHP Notice:  Undefined offset: 0 in C:\php\rss-bridge\index.php on line 249
2018-12-28 16:25:56 +01:00
logmanoriginal
719320e1a4 travis: Fail on deprecation warning
This commit makes Travis fail on deprecation warnings for new versions
of PHP and ensures that checks are made against all versions from PHP
5.6 onwards
2018-12-28 16:15:36 +01:00
triatic
81ee15a161 general: Fix PHP 7.3 deprecation warnings (#982)
Fix PHP 7.3 deprecation warnings. FILTER_VALIDATE_URL implies FILTER_FLAG_SCHEME_REQUIRED and FILTER_FLAG_HOST_REQUIRED since PHP 5.2.1

https://bugs.php.net/bug.php?id=75442
2018-12-28 16:13:03 +01:00
LogMANOriginal
988635dcf3 core: Add FeedItem class (#940)
Add transformation from legacy items to FeedItems, before transforming
items to the desired format. This allows using legacy bridges alongside
bridges that return FeedItems.

As discussed in #940, instead of throwing exceptions on invalid
parameters, add messages to the debug log instead

Add support for strings to setTimestamp(). If the provided timestamp
is a string, automatically try to parse it using strtotime().

This allows bridges to simply use `$item['timestamp'] = $timestamp;`
instead of `$item['timestamp'] = strtotime($timestamp);`

Support simple_html_dom_node as input paramter for setURI

Support simple_html_dom_node as input parameter for setContent
2018-12-26 22:41:32 +01:00
triatic
4095cad9b4 lib: Make cURL module requirement optional (#979)
When running in CLI mode without certificates, do not require curl module to be loaded.
2018-12-26 22:31:30 +01:00
logmanoriginal
e7d3a006c8 global: Fix code violations 2018-12-26 21:58:07 +01:00
logmanoriginal
ce65f51d91 [phpcs] Fix blank line detection
Squiz.WhiteSpace.SuperfluousWhitespace has problems detecting blank
lines in functions when used together with the PSR2 standard.

More information: https://github.com/squizlabs/PHP_CodeSniffer/issues/600

This commit fixes that issue by restoring the original behavior.
It also adds rules for function spacing because the sniff mentioned
above does only work within functions.
2018-12-26 21:39:37 +01:00
Roliga
4b22862295 [DerpibooruBridge] Add new bridge (#949)
New bridge for the derpibooru.org image board.
2018-12-26 21:14:04 +01:00
fulmeek
185a773e74 [DilbertBridge] Fixed URI and item title (#976) 2018-12-26 21:11:45 +01:00
fulmeek
10659dd453 [ModelKarteiBridge] Add new bridge (#975) 2018-12-26 21:10:00 +01:00
fulmeek
6b2a45c1e8 [OneFortuneADayBridge] Add new bridge (#974) 2018-12-26 21:06:16 +01:00
fulmeek
6e4b6fa1cc [OsmAndBlogBridge] Add new bridge (#973) 2018-12-26 20:55:38 +01:00
ORelio
0cad5f24e6 [TheHackerNews] Fix content extraction (#972) 2018-12-26 20:47:02 +01:00
Roliga
cb6ad7c077 [TrelloBridge] Add new bridge (#971)
Adds a new bridge for activity on boards and cards on the trello.com task management site.
2018-12-26 20:44:53 +01:00
Roliga
4438807b26 [SoundcloudBridge] Fix for artists with few tracks (#970)
Artists with less than 10 tracks would return blank articles. This fixes that.
2018-12-26 20:35:05 +01:00
Lorenzo Stanco
6c1d861529 [InstagramBridge] Add link on image and video indication in title (#966)
In item content, the image is now a clickable link to the post;
In item title a ▶ is prepended if the post contains a video; it's impossible to tell from the content image.
2018-12-26 20:32:44 +01:00
triatic
dc83962483 [contents] Use file_get_contents when in CLI mode & no certs (#962)
file_get_contents can natively use system root certificates, so use file_get_contents when in CLI mode with no root certificates for cURL.
2018-12-26 20:04:55 +01:00
logmanoriginal
bb2329fa3a [TwitterBridge] Add option to disable image scaling in feeds
Images in Twitter feeds are currently being scaled by adding ':orig'
(original image) and ':thumb' (thumbnail) to image URIs in the feed.

This can cause issues with feed readers that don't handle colons in
URIs correctly.

Image scaling can now be disabled by adding '&noimgscaling=on' to the
query. This parameter is optional to stay compatible to existing feeds.

References #957
2018-12-12 17:00:12 +01:00
Lorenzo Stanco
758f37b452 [InstagramBridge] Truncate long titles and use full text as content (#961)
- Truncate long titles and use full text as content (using only the first line of text content as title)
2018-12-12 16:44:37 +01:00
logmanoriginal
fb8a064e3a [simplehtmldom] Increase MAX_FILE_SIZE to 10 MB
This fixes an issue where larger pages could not be loaded
because the size limit is too small
2018-12-11 17:16:35 +01:00
logmanoriginal
b00971b2c3 [simplehtmldom] Update parser to version 1.7
- Update parser to version 1.7
https://sourceforge.net/projects/simplehtmldom/files/simplehtmldom/1.7/

References #959

-------------------- CHANGELOG --------------------

- Added code documentation to improve readability
- Added unit tests for `simple_html_dom::$self_closing_tags`
- Added unit tests for `simple_html_dom::$optional_closing_tags`
- Added unit tests for bug reports
  - Added test for bug [#56](https://sourceforge.net/p/simplehtmldom/bugs/56/)
  - Added test for bug [#97](https://sourceforge.net/p/simplehtmldom/bugs/97/)
  - Added test for bug [#116](https://sourceforge.net/p/simplehtmldom/bugs/116/)
  - Added test for bug [#121](https://sourceforge.net/p/simplehtmldom/bugs/127/)
  - Added test for bug [#127](https://sourceforge.net/p/simplehtmldom/bugs/127/)
  - Added test for bug [#154](https://sourceforge.net/p/simplehtmldom/bugs/154/)
  - Added test for bug [#160](https://sourceforge.net/p/simplehtmldom/bugs/160/)
- Added unit tests for memory management of the parser
- Added bit flags to `simple_html_dom::load()`
  - Added bit flag `HDOM_SMARTY_AS_TEXT` to optionally filter Smarty scripts (#154)\
  **Note**: Smarty scripts are no longer filtered by default!\
- Added build script to automate releases
- Added support for attributes without whitespace to separate them
- Improved documentation and readability for `$self_closing_tags`
- Improved documentation and readability for `$block_tags`
- Improved documentation and readability for `$optional_closing_tags`
- Updated list of `simple_html_dom::$self_closing_tags`
  - Removed 'spacer' (obsolete)
  - Added 'area'
  - Added 'col'
  - Added 'meta'
  - Added 'param'
  - Added 'source'
  - Added 'track'
  - Added 'wbr'
- Updated list of `simple_html_dom::$optional_closing_tags`
  - Removed "nobr" (obsolete)
  - Added 'th' as closable element to 'td'
  - Added 'td' as closable element to 'th'
  - Added 'optgroup' with 'optgroup' and 'option' as closable elements
  - Added 'optgroup' as closable element to 'option'
  - Added 'rp' with 'rp' and 'rt' as closable elements
  - Added 'rt' with 'rt' and 'rp' as closable elements
- Clarified meaning of `simple_html_dom->parent`
- Changed default `$offset` for `file_get_html()` from -1 to 0 (#161)
- Changed `simple_html_dom::load()` to remove script tags before replacing newline characters
- `simple_html_dom_node::text()` no longer adds whitespace to top level span elements (only to sub-elements)
- `simple_html_dom_node::text()` adds blank lines between paragraphs
- Normalized line endings in the repository to LF via `.gitattributes`
- Improved performance of `simple_html_dom::parse_charset()` by approximately 25%
- Improved performance of `simple_html_dom::parse()` by approximately 10%
- `str_get_html()` is deprecated and should be replaced by `new simple_html_dom()`
- Removed protected function `simple_html_dom::copy_until_char_escaped()`
- Fixed compatibility issues with PHP 7.3
- Fixed typo (#147)
- Fixed handling of incorrectly escaped text (#160)
- Restore functionality of `$maxLen` in `file_get_html()`
- Fixed load_file breaks if an error ocurred in another script
2018-12-11 17:15:38 +01:00
logmanoriginal
a07ead42a7 Bump version to dev.2018-12-11 2018-12-11 17:07:41 +01:00
139 changed files with 7525 additions and 1631 deletions

View File

@@ -19,7 +19,7 @@ script:
- ~/.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;
~/.config/composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --extensions=php -p;
fi
# Run unit tests (stable)
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
@@ -29,7 +29,7 @@ script:
# 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-;
~/.config/composer/vendor/bin/phpcs . --standard=PHPCompatibility --extensions=php -p --runtime-set testVersion 5.6-;
fi
matrix:

View File

@@ -66,6 +66,7 @@ RSS-Bridge requires PHP 5.6 or higher with following extensions enabled:
- [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php)
- [`curl`](https://secure.php.net/manual/en/book.curl.php)
- [`json`](https://secure.php.net/manual/en/book.json.php)
- [`sqlite3`](http://php.net/manual/en/book.sqlite3.php) (only when using SQLiteCache)
Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
@@ -111,41 +112,16 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
-->
* [16mhz](https://github.com/16mhz)
* [adamchainz](https://github.com/adamchainz)
* [Ahiles3005](https://github.com/Ahiles3005)
* [Albirew](https://github.com/Albirew)
* [aledeg](https://github.com/aledeg)
* [alexAubin](https://github.com/alexAubin)
* [AmauryCarrade](https://github.com/AmauryCarrade)
* [AntoineTurmel](https://github.com/AntoineTurmel)
* [ArthurHoaro](https://github.com/ArthurHoaro)
* [Astalaseven](https://github.com/Astalaseven)
* [Astyan-42](https://github.com/Astyan-42)
* [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)
@@ -156,20 +132,38 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [corenting](https://github.com/corenting)
* [couraudt](https://github.com/couraudt)
* [da2x](https://github.com/da2x)
* [Daiyousei](https://github.com/Daiyousei)
* [disk0x](https://github.com/disk0x)
* [eMerzh](https://github.com/eMerzh)
* [Djuuu](https://github.com/Djuuu)
* [Draeli](https://github.com/Draeli)
* [em92](https://github.com/em92)
* [eMerzh](https://github.com/eMerzh)
* [EtienneM](https://github.com/EtienneM)
* [fluffy-critter](https://github.com/fluffy-critter)
* [Frenzie](https://github.com/Frenzie)
* [fulmeek](https://github.com/fulmeek)
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
* [Glandos](https://github.com/Glandos)
* [GregThib](https://github.com/GregThib)
* [griffaurel](https://github.com/griffaurel)
* [Grummfy](https://github.com/Grummfy)
* [hunhejj](https://github.com/hunhejj)
* [j0k3r](https://github.com/j0k3r)
* [JackNUMBER](https://github.com/JackNUMBER)
* [jdigilio](https://github.com/jdigilio)
* [JeremyRand](https://github.com/JeremyRand)
* [Jocker666z](https://github.com/Jocker666z)
* [klimplant](https://github.com/klimplant)
* [kranack](https://github.com/kranack)
* [kraoc](https://github.com/kraoc)
* [l1n](https://github.com/l1n)
* [laBecasse](https://github.com/laBecasse)
* [lagaisse](https://github.com/lagaisse)
* [lalannev](https://github.com/lalannev)
* [ldidry](https://github.com/ldidry)
* [Limero](https://github.com/Limero)
* [LogMANOriginal](https://github.com/LogMANOriginal)
* [lorenzos](https://github.com/lorenzos)
* [m0zes](https://github.com/m0zes)
* [matthewseal](https://github.com/matthewseal)
* [mcbyte-it](https://github.com/mcbyte-it)
@@ -178,28 +172,40 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [metaMMA](https://github.com/metaMMA)
* [mickael-bertrand](https://github.com/mickael-bertrand)
* [mitsukarenai](https://github.com/mitsukarenai)
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
* [mr-flibble](https://github.com/mr-flibble)
* [mro](https://github.com/mro)
* [mxmehl](https://github.com/mxmehl)
* [nel50n](https://github.com/nel50n)
* [niawag](https://github.com/niawag)
* [Nono-m0le](https://github.com/Nono-m0le)
* [ORelio](https://github.com/ORelio)
* [PaulVayssiere](https://github.com/PaulVayssiere)
* [pellaeon](https://github.com/pellaeon)
* [Piranhaplant](https://github.com/Piranhaplant)
* [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)
* [Riduidel](https://github.com/Riduidel)
* [rogerdc](https://github.com/rogerdc)
* [Roliga](https://github.com/Roliga)
* [sebsauvage](https://github.com/sebsauvage)
* [somini](https://github.com/somini)
* [squeek502](https://github.com/squeek502)
* [Strubbl](https://github.com/Strubbl)
* [sublimz](https://github.com/sublimz)
* [sysadminstory](https://github.com/sysadminstory)
* [tameroski](https://github.com/tameroski)
* [teromene](https://github.com/teromene)
* [TheRadialActive](https://github.com/TheRadialActive)
* [triatic](https://github.com/triatic)
* [WalterBarrett](https://github.com/WalterBarrett)
* [wtuuju](https://github.com/wtuuju)
* [yardenac](https://github.com/yardenac)
* [ZeNairolf](https://github.com/ZeNairolf)
Licenses
===

50
actions/DetectAction.php Normal file
View File

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

234
actions/DisplayAction.php Normal file
View File

@@ -0,0 +1,234 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class DisplayAction extends ActionAbstract {
public function execute() {
$bridge = array_key_exists('bridge', $this->userData) ? $this->userData['bridge'] : null;
$format = $this->userData['format']
or returnClientError('You must specify a format!');
// DEPRECATED: 'nameFormat' scheme is replaced by 'name' in format parameter values
// this is to keep compatibility until futher complete removal
if(($pos = strpos($format, 'Format')) === (strlen($format) - strlen('Format'))) {
$format = substr($format, 0, $pos);
}
// whitelist control
if(!Bridge::isWhitelisted($bridge)) {
throw new \Exception('This bridge is not whitelisted', 401);
die;
}
// Data retrieval
$bridge = Bridge::create($bridge);
$noproxy = array_key_exists('_noproxy', $this->userData)
&& filter_var($this->userData['_noproxy'], FILTER_VALIDATE_BOOLEAN);
if(defined('PROXY_URL') && PROXY_BYBRIDGE && $noproxy) {
define('NOPROXY', true);
}
// Cache timeout
$cache_timeout = -1;
if(array_key_exists('_cache_timeout', $this->userData)) {
if(!CUSTOM_CACHE_TIMEOUT) {
unset($this->userData['_cache_timeout']);
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($this->userData);
header('Location: ' . $uri, true, 301);
die();
}
$cache_timeout = filter_var($this->userData['_cache_timeout'], FILTER_VALIDATE_INT);
} else {
$cache_timeout = $bridge->getCacheTimeout();
}
// Remove parameters that don't concern bridges
$bridge_params = array_diff_key(
$this->userData,
array_fill_keys(
array(
'action',
'bridge',
'format',
'_noproxy',
'_cache_timeout',
'_error_time'
), '')
);
// Remove parameters that don't concern caches
$cache_params = array_diff_key(
$this->userData,
array_fill_keys(
array(
'action',
'format',
'_noproxy',
'_cache_timeout',
'_error_time'
), '')
);
// Initialize cache
$cache = Cache::create(Configuration::getConfig('cache', 'type'));
$cache->setPath(PATH_CACHE);
$cache->purgeCache(86400); // 24 hours
$cache->setParameters($cache_params);
$items = array();
$infos = array();
$mtime = $cache->getTime();
if($mtime !== false
&& (time() - $cache_timeout < $mtime)
&& !Debug::isEnabled()) { // Load cached data
// Send "Not Modified" response if client supports it
// Implementation based on https://stackoverflow.com/a/10847262
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
if($mtime <= $stime) { // Cached data is older or same
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304);
die();
}
}
$cached = $cache->loadData();
if(isset($cached['items']) && isset($cached['extraInfos'])) {
foreach($cached['items'] as $item) {
$items[] = new \FeedItem($item);
}
$infos = $cached['extraInfos'];
}
} else { // Collect new data
try {
$bridge->setDatas($bridge_params);
$bridge->collectData();
$items = $bridge->getItems();
// Transform "legacy" items to FeedItems if necessary.
// Remove this code when support for "legacy" items ends!
if(isset($items[0]) && is_array($items[0])) {
$feedItems = array();
foreach($items as $item) {
$feedItems[] = new \FeedItem($item);
}
$items = $feedItems;
}
$infos = array(
'name' => $bridge->getName(),
'uri' => $bridge->getURI(),
'icon' => $bridge->getIcon()
);
} catch(Error $e) {
error_log($e);
$item = new \FeedItem();
// Create "new" error message every 24 hours
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
// Error 0 is a special case (i.e. "trying to get property of non-object")
if($e->getCode() === 0) {
$item->setTitle(
'Bridge encountered an unexpected situation! ('
. $this->userData['_error_time']
. ')'
);
} else {
$item->setTitle(
'Bridge returned error '
. $e->getCode()
. '! ('
. $this->userData['_error_time']
. ')'
);
}
$item->setURI(
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
. '?'
. http_build_query($this->userData)
);
$item->setTimestamp(time());
$item->setContent(buildBridgeException($e, $bridge));
$items[] = $item;
} catch(Exception $e) {
error_log($e);
$item = new \FeedItem();
// Create "new" error message every 24 hours
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
$item->setURI(
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
. '?'
. http_build_query($this->userData)
);
$item->setTitle(
'Bridge returned error '
. $e->getCode()
. '! ('
. $this->userData['_error_time']
. ')'
);
$item->setTimestamp(time());
$item->setContent(buildBridgeException($e, $bridge));
$items[] = $item;
}
// Store data in cache
$cache->saveData(array(
'items' => array_map(function($i){ return $i->toArray(); }, $items),
'extraInfos' => $infos
));
}
// Data transformation
try {
$format = Format::create($format);
$format->setItems($items);
$format->setExtraInfos($infos);
$format->setLastModified($cache->getTime());
$format->display();
} catch(Error $e) {
error_log($e);
header('Content-Type: text/html', true, $e->getCode());
die(buildTransformException($e, $bridge));
} catch(Exception $e) {
error_log($e);
header('Content-Type: text/html', true, $e->getCode());
die(buildTransformException($e, $bridge));
}
}
}

53
actions/ListAction.php Normal file
View File

@@ -0,0 +1,53 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class ListAction extends ActionAbstract {
public function execute() {
$list = new StdClass();
$list->bridges = array();
$list->total = 0;
foreach(Bridge::getBridgeNames() as $bridgeName) {
$bridge = Bridge::create($bridgeName);
if($bridge === false) { // Broken bridge, show as inactive
$list->bridges[$bridgeName] = array(
'status' => 'inactive'
);
continue;
}
$status = Bridge::isWhitelisted($bridgeName) ? 'active' : 'inactive';
$list->bridges[$bridgeName] = array(
'status' => $status,
'uri' => $bridge->getURI(),
'name' => $bridge->getName(),
'icon' => $bridge->getIcon(),
'parameters' => $bridge->getParameters(),
'maintainer' => $bridge->getMaintainer(),
'description' => $bridge->getDescription()
);
}
$list->total = count($list->bridges);
header('Content-Type: application/json');
echo json_encode($list, JSON_PRETTY_PRINT);
}
}

View File

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

View File

@@ -10,7 +10,6 @@ class AllocineFRBridge extends BridgeAbstract {
'category' => array(
'name' => 'category',
'type' => 'list',
'required' => true,
'exampleValue' => 'Faux Raccord',
'title' => 'Select your category',
'values' => array(
@@ -83,5 +82,4 @@ class AllocineFRBridge extends BridgeAbstract {
}
}
}
}

View File

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

View File

@@ -19,7 +19,6 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
'tld' => array(
'name' => 'Country',
'type' => 'list',
'required' => true,
'values' => array(
'Australia' => 'com.au',
'Brazil' => 'com.br',

View File

@@ -137,5 +137,4 @@ class AnimeUltimeBridge extends BridgeAbstract {
return parent::getName();
}
}

View File

@@ -0,0 +1,62 @@
<?php
class AppleMusicBridge extends BridgeAbstract {
const NAME = 'Apple Music';
const URI = 'https://www.apple.com';
const DESCRIPTION = 'Fetches the latest releases from an artist';
const MAINTAINER = 'Limero';
const PARAMETERS = [[
'url' => [
'name' => 'Artist URL',
'exampleValue' => 'https://itunes.apple.com/us/artist/dunderpatrullen/329796274',
'required' => true,
],
'imgSize' => [
'name' => 'Image size for thumbnails (in px)',
'type' => 'number',
'defaultValue' => 512,
'required' => true,
]
]];
const CACHE_TIMEOUT = 21600; // 6 hours
public function collectData() {
$url = $this->getInput('url');
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request: ' . $url);
$imgSize = $this->getInput('imgSize');
// Grab the json data from the page
$html = $html->find('script[id=shoebox-ember-data-store]', 0);
$html = strstr($html, '{');
$html = substr($html, 0, -9);
$json = json_decode($html);
// Loop through each object
foreach ($json->included as $obj) {
if ($obj->type === 'lockup/album') {
$this->items[] = [
'title' => $obj->attributes->artistName . ' - ' . $obj->attributes->name,
'uri' => $obj->attributes->url,
'timestamp' => $obj->attributes->releaseDate,
'enclosures' => $obj->relationships->artwork->data->id,
];
} elseif ($obj->type === 'image') {
$images[$obj->id] = $obj->attributes->url;
}
}
// Add the images to each item
foreach ($this->items as &$item) {
$item['enclosures'] = [
str_replace('{w}x{h}bb.{f}', $imgSize . 'x0w.jpg', $images[$item['enclosures']]),
];
}
// Sort the order to put the latest albums first
usort($this->items, function($a, $b){
return $a['timestamp'] < $b['timestamp'];
});
}
}

View File

@@ -119,5 +119,4 @@ class Arte7Bridge extends BridgeAbstract {
$this->items[] = $item;
}
}
}

View File

@@ -0,0 +1,72 @@
<?php
class AsahiShimbunAJWBridge extends BridgeAbstract {
const NAME = 'Asahi Shimbun AJW';
const BASE_URI = 'http://www.asahi.com';
const URI = self::BASE_URI . '/ajw/';
const DESCRIPTION = 'Asahi Shimbun - Asia & Japan Watch';
const MAINTAINER = 'somini';
const PARAMETERS = array(
array(
'section' => array(
'type' => 'list',
'name' => 'Section',
'values' => array(
'Japan » Social Affairs' => 'japan/social',
'Japan » People' => 'japan/people',
'Japan » 3/11 Disaster' => 'japan/0311disaster',
'Japan » Sci & Tech' => 'japan/sci_tech',
'Politics' => 'politics',
'Business' => 'business',
'Culture » Style' => 'culture/style',
'Culture » Movies' => 'culture/movies',
'Culture » Manga & Anime' => 'culture/manga_anime',
'Asia » China' => 'asia/china',
'Asia » Korean Peninsula' => 'asia/korean_peninsula',
'Asia » Around Asia' => 'asia/around_asia',
'Opinion » Editorial' => 'opinion/editorial',
'Opinion » Vox Populi' => 'opinion/vox',
),
'defaultValue' => 'Politics',
)
)
);
private function getSectionURI($section) {
return self::getURI() . $section . '/';
}
public function collectData() {
$html = getSimpleHTMLDOM($this->getSectionURI($this->getInput('section')))
or returnServerError('Could not load content');
foreach($html->find('#MainInner li a') as $element) {
if ($element->parent()->class == 'HeadlineTopImage-S') {
Debug::log('Skip Headline, it is repeated below');
continue;
}
$item = array();
$item['uri'] = self::BASE_URI . $element->href;
$e_lead = $element->find('span.Lead', 0);
if ($e_lead) {
$item['content'] = $e_lead->innertext;
$e_lead->outertext = '';
} else {
$item['content'] = $element->innertext;
}
$e_date = $element->find('span.EnDate', 0);
if ($e_date) {
$item['timestamp'] = strtotime($e_date->innertext);
$e_date->outertext = '';
}
$e_video = $element->find('span.EnVideo', 0);
if ($e_video) {
$e_video->outertext = '';
$element->innertext = "VIDEO: $element->innertext";
}
$item['title'] = $element->innertext;
$this->items[] = $item;
}
}
}

View File

@@ -0,0 +1,99 @@
<?php
class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
const NAME = 'Baka Updates Manga Releases';
const URI = 'https://www.mangaupdates.com/';
const DESCRIPTION = 'Get the latest series releases';
const MAINTAINER = 'fulmeek';
const PARAMETERS = array(array(
'series_id' => array(
'name' => 'Series ID',
'type' => 'number',
'required' => true,
'exampleValue' => '12345'
)
));
const LIMIT_COLS = 5;
const LIMIT_ITEMS = 10;
private $feedName = '';
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Series not found');
// content is an unstructured pile of divs, ugly to parse
$cols = $html->find('div#main_content div.row > div.text');
if (!$cols)
returnServerError('No releases');
$rows = array_slice(
array_chunk($cols, self::LIMIT_COLS), 0, self::LIMIT_ITEMS
);
if (isset($rows[0][1])) {
$this->feedName = $this->filterHTML($rows[0][1]->plaintext);
}
foreach($rows as $cols) {
if (count($cols) < self::LIMIT_COLS) continue;
$item = array();
$title = array();
$item['content'] = '';
$objDate = $cols[0];
if ($objDate)
$item['timestamp'] = strtotime($objDate->plaintext);
$objTitle = $cols[1];
if ($objTitle) {
$title[] = $this->filterHTML($objTitle->plaintext);
$item['content'] .= '<p>Series: ' . $this->filterText($objTitle->innertext) . '</p>';
}
$objVolume = $cols[2];
if ($objVolume && !empty($objVolume->plaintext))
$title[] = 'Vol.' . $objVolume->plaintext;
$objChapter = $cols[3];
if ($objChapter && !empty($objChapter->plaintext))
$title[] = 'Chp.' . $objChapter->plaintext;
$objAuthor = $cols[4];
if ($objAuthor && !empty($objAuthor->plaintext)) {
$item['author'] = $this->filterHTML($objAuthor->plaintext);
$item['content'] .= '<p>Groups: ' . $this->filterText($objAuthor->innertext) . '</p>';
}
$item['title'] = implode(' ', $title);
$item['uri'] = $this->getURI();
$item['uid'] = hash('sha1', $item['title']);
$this->items[] = $item;
}
}
public function getURI(){
$series_id = $this->getInput('series_id');
if (!empty($series_id)) {
return self::URI . 'releases.html?search=' . $series_id . '&stype=series';
}
return self::URI;
}
public function getName(){
if(!empty($this->feedName)) {
return $this->feedName . ' - ' . self::NAME;
}
return parent::getName();
}
private function filterText($text) {
return rtrim($text, '*');
}
private function filterHTML($text) {
return $this->filterText(html_entity_decode($text));
}
}

View File

@@ -13,48 +13,72 @@ class BandcampBridge extends BridgeAbstract {
'required' => true
)
));
const IMGURI = 'https://f4.bcbits.com/';
const IMGSIZE_300PX = 23;
const IMGSIZE_700PX = 16;
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.');
$url = self::URI . 'api/hub/1/dig_deeper';
$data = $this->buildRequestJson();
$header = array(
'Content-Type: application/json',
'Content-Length: ' . strlen($data)
);
$opts = array(
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $data
);
$content = getContents($url, $header, $opts)
or returnServerError('Could not complete request to: ' . $url);
foreach($html->find('li.item') as $release) {
$script = $release->find('div.art', 0)->getAttribute('onclick');
$uri = ltrim($script, "return 'url(");
$uri = rtrim($uri, "')");
$json = json_decode($content);
$item = array();
$item['author'] = $release->find('div.itemsubtext', 0)->plaintext
. ' - '
. $release->find('div.itemtext', 0)->plaintext;
if ($json->ok !== true) {
returnServerError('Invalid response');
}
$item['title'] = $release->find('div.itemsubtext', 0)->plaintext
. ' - '
. $release->find('div.itemtext', 0)->plaintext;
foreach ($json->items as $entry) {
$url = $entry->tralbum_url;
$artist = $entry->artist;
$title = $entry->title;
// e.g. record label is the releaser, but not the artist
$releaser = $entry->band_name !== $entry->artist ? $entry->band_name : null;
$item['content'] = '<img src="'
. $uri
. '"/><br/>'
. $release->find('div.itemsubtext', 0)->plaintext
. ' - '
. $release->find('div.itemtext', 0)->plaintext;
$full_title = $artist . ' - ' . $title;
$full_artist = $artist;
if (isset($releaser)) {
$full_title .= ' (' . $releaser . ')';
$full_artist .= ' (' . $releaser . ')';
}
$small_img = $this->getImageUrl($entry->art_id, self::IMGSIZE_300PX);
$img = $this->getImageUrl($entry->art_id, self::IMGSIZE_700PX);
$item['id'] = $release->find('a', 0)->getAttribute('href');
$item['uri'] = $release->find('a', 0)->getAttribute('href');
$item = array(
'uri' => $url,
'author' => $full_artist,
'title' => $full_title
);
$item['content'] = "<img src='$small_img' /><br/>$full_title";
$item['enclosures'] = array($img);
$this->items[] = $item;
}
}
public function getURI(){
if(!is_null($this->getInput('tag'))) {
return self::URI . 'tag/' . urlencode($this->getInput('tag')) . '?sort_field=date';
}
private function buildRequestJson(){
$requestJson = array(
'tag' => $this->getInput('tag'),
'page' => 1,
'sort' => 'date'
);
return json_encode($requestJson);
}
return parent::getURI();
private function getImageUrl($id, $size){
return self::IMGURI . 'img/a' . $id . '_' . $size . '.jpg';
}
public function getName(){

View File

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

View File

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

134
bridges/CachetBridge.php Normal file
View File

@@ -0,0 +1,134 @@
<?php
class CachetBridge extends BridgeAbstract {
const NAME = 'Cachet Bridge';
const URI = 'https://cachethq.io/';
const DESCRIPTION = 'Returns status updates from any Cachet installation';
const MAINTAINER = 'klimplant';
const PARAMETERS = array(
array(
'host' => array(
'name' => 'Cachet installation',
'type' => 'text',
'required' => true,
'title' => 'The URL of the Cachet installation',
'exampleValue' => 'https://demo.cachethq.io/',
), 'additional_info' => array(
'name' => 'Additional Timestamps',
'type' => 'checkbox',
'title' => 'Whether to include the given timestamps'
)
)
);
const CACHE_TIMEOUT = 300;
private $componentCache = [];
public function getURI() {
return $this->getInput('host') === null ? 'https://cachethq.io/' : $this->getInput('host');
}
/**
* Validates the ping request to the cache API
*
* @param string $ping
* @return boolean
*/
private function validatePing($ping) {
$ping = json_decode($ping);
if ($ping === null) {
return false;
}
return $ping->data === 'Pong!';
}
/**
* Returns the component name of a cachat component
*
* @param integer $id
* @return string
*/
private function getComponentName($id) {
if ($id === 0) {
return '';
}
if (array_key_exists($id, $this->componentCache)) {
return $this->componentCache[$id];
}
$component = getContents($this->getURI() . '/api/v1/components/' . $id);
$component = json_decode($component);
if ($component === null) {
return '';
}
return $component->data->name;
}
public function collectData() {
$ping = getContents(urljoin($this->getURI(), '/api/v1/ping'));
if (!$this->validatePing($ping)) {
returnClientError('Provided URI is invalid!');
}
$url = urljoin($this->getURI(), '/api/v1/incidents?sort=id&order=desc');
$incidents = getContents($url);
$incidents = json_decode($incidents);
if ($incidents === null) {
returnClientError('/api/v1/incidents returned no valid json');
}
usort($incidents->data, function ($a, $b) {
$timeA = strtotime($a->updated_at);
$timeB = strtotime($b->updated_at);
return $timeA > $timeB ? -1 : 1;
});
foreach ($incidents->data as $incident) {
if (isset($incident->permalink)) {
$permalink = $incident->permalink;
} else {
$permalink = urljoin($this->getURI(), '/incident/' . $incident->id);
}
$title = $incident->human_status . ': ' . $incident->name;
$message = '';
if ($this->getInput('additional_info')) {
if (isset($incident->occurred_at)) {
$message .= 'Occurred at: ' . $incident->occurred_at . "\r\n";
}
if (isset($incident->scheduled_at)) {
$message .= 'Scheduled at: ' . $incident->scheduled_at . "\r\n";
}
if (isset($incident->created_at)) {
$message .= 'Created at: ' . $incident->created_at . "\r\n";
}
if (isset($incident->updated_at)) {
$message .= 'Updated at: ' . $incident->updated_at . "\r\n\r\n";
}
}
$message .= $incident->message;
$content = nl2br($message);
$componentName = $this->getComponentName($incident->component_id);
$uidOrig = $permalink . $incident->created_at;
$uid = hash('sha512', $uidOrig);
$timestamp = strtotime($incident->created_at);
$categories = [];
$categories[] = $incident->human_status;
if ($componentName !== '') {
$categories[] = $componentName;
}
$item = [];
$item['uri'] = $permalink;
$item['title'] = $title;
$item['timestamp'] = $timestamp;
$item['content'] = $content;
$item['uid'] = $uid;
$item['categories'] = $categories;
$this->items[] = $item;
}
}
}

View File

@@ -0,0 +1,22 @@
<?php
class ComboiosDePortugalBridge extends BridgeAbstract {
const NAME = 'CP | Avisos';
const BASE_URI = 'https://www.cp.pt';
const URI = self::BASE_URI . '/passageiros/pt';
const DESCRIPTION = 'Comboios de Portugal | Avisos';
const MAINTAINER = 'somini';
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI() . '/consultar-horarios/avisos')
or returnServerError('Could not load content');
foreach($html->find('.warnings-table a') as $element) {
$item = array();
$item['title'] = $element->innertext;
$item['uri'] = self::BASE_URI . $element->href;
$this->items[] = $item;
}
}
}

View File

@@ -15,7 +15,6 @@ class ContainerLinuxReleasesBridge extends BridgeAbstract {
'channel' => [
'name' => 'Release Channel',
'type' => 'list',
'required' => true,
'defaultValue' => self::STABLE,
'values' => [
'Stable' => self::STABLE,

View File

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

View File

@@ -6,72 +6,75 @@ class DemonoidBridge extends BridgeAbstract {
const URI = 'https://www.demonoid.pw/';
const DESCRIPTION = 'Returns results from search';
const PARAMETERS = array(array(
'q' => array(
'name' => 'keywords',
'exampleValue' => 'keyword1 keyword2…',
'required' => true,
const PARAMETERS = array(
'Keywords' => array(
'q' => array(
'name' => 'keywords',
'exampleValue' => 'keyword1 keyword2…',
'required' => true,
),
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 0,
'Movies' => 1,
'Music' => 2,
'TV' => 3,
'Games' => 4,
'Applications' => 5,
'Pictures' => 8,
'Anime' => 9,
'Comics' => 10,
'Books' => 11,
'Audiobooks' => 17
)
)
),
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 0,
'Movies' => 1,
'Music' => 2,
'TV' => 3,
'Games' => 4,
'Applications' => 5,
'Pictures' => 8,
'Anime' => 9,
'Comics' => 10,
'Books' => 11,
'Audiobooks' => 17
'Category Only' => array(
'catOnly' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 0,
'Movies' => 1,
'Music' => 2,
'TV' => 3,
'Games' => 4,
'Applications' => 5,
'Pictures' => 8,
'Anime' => 9,
'Comics' => 10,
'Books' => 11,
'Audiobooks' => 17
)
)
)
), array(
'catOnly' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 0,
'Movies' => 1,
'Music' => 2,
'TV' => 3,
'Games' => 4,
'Applications' => 5,
'Pictures' => 8,
'Anime' => 9,
'Comics' => 10,
'Books' => 11,
'Audiobooks' => 17
)
)
), array(
'userid' => array(
'name' => 'user id',
'exampleValue' => '00000',
'required' => true,
'type' => 'number'
),
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 0,
'Movies' => 1,
'Music' => 2,
'TV' => 3,
'Games' => 4,
'Applications' => 5,
'Pictures' => 8,
'Anime' => 9,
'Comics' => 10,
'Books' => 11,
'Audiobooks' => 17
'User ID' => array(
'userid' => array(
'name' => 'user id',
'exampleValue' => '00000',
'required' => true,
'type' => 'number'
),
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 0,
'Movies' => 1,
'Music' => 2,
'TV' => 3,
'Games' => 4,
'Applications' => 5,
'Pictures' => 8,
'Anime' => 9,
'Comics' => 10,
'Books' => 11,
'Audiobooks' => 17
)
)
)
)
);
public function collectData() {

View File

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

View File

@@ -15,7 +15,6 @@ class DesoutterBridge extends BridgeAbstract {
'news_lang' => array(
'name' => 'Language',
'type' => 'list',
'required' => true,
'title' => 'Select your language',
'defaultValue' => 'Corporate',
'values' => array(
@@ -66,7 +65,6 @@ class DesoutterBridge extends BridgeAbstract {
'industry_lang' => array(
'name' => 'Language',
'type' => 'list',
'required' => true,
'title' => 'Select your language',
'defaultValue' => 'Corporate',
'values' => array(
@@ -117,7 +115,6 @@ class DesoutterBridge extends BridgeAbstract {
'full' => array(
'name' => 'Load full articles',
'type' => 'checkbox',
'required' => false,
'title' => 'Enable to load the full article for each item'
)
)
@@ -236,5 +233,4 @@ class DesoutterBridge extends BridgeAbstract {
echo $list;
}
}

View File

@@ -22,8 +22,7 @@ class DevToBridge extends BridgeAbstract {
'name' => 'Full article',
'type' => 'checkbox',
'required' => false,
'title' => 'Enable to receive the full article for each item',
'defaultValue' => false
'title' => 'Enable to receive the full article for each item'
)
)
);
@@ -101,5 +100,4 @@ EOD;
return $html->find('[id="article-body"]', 0);
}
}

View File

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

View File

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

View File

@@ -6,25 +6,36 @@ class EliteDangerousGalnetBridge extends BridgeAbstract {
const URI = 'https://community.elitedangerous.com/galnet/';
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';
}
const PARAMETERS = array(
array(
'language' => array(
'name' => 'Language',
'type' => 'list',
'values' => array(
'English' => 'en',
'French' => 'fr',
'German' => 'de'
),
'defaultValue' => 'en'
)
)
);
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
$language = $this->getInput('language');
$url = 'https://community.elitedangerous.com/';
$url = $url . $language . '/galnet';
$html = getSimpleHTMLDOM($url)
or returnServerError('Error while downloading the website content');
foreach($html->find('div.article') as $element) {
$item = array();
$uri = $element->find('h3 a', 0)->href;
$uri = self::URI . substr($uri, strlen('/galnet/'));
$uri = 'https://community.elitedangerous.com/' . $language . $uri;
$item['uri'] = $uri;
$title = $element->find('h3 a', 0)->plaintext;
$item['title'] = substr($title, 1); //remove the space between icon and title
$item['title'] = $element->find('h3 a', 0)->plaintext;
$content = $element->find('p', -1)->innertext;
$item['content'] = $content;

View File

@@ -120,7 +120,7 @@ class ElloBridge extends BridgeAbstract {
}
private function getAPIKey() {
$cache = Cache::create('FileCache');
$cache = Cache::create(Configuration::getConfig('cache', 'type'));
$cache->setPath(PATH_CACHE);
$cache->setParameters(['key']);
$key = $cache->loadData();
@@ -143,5 +143,4 @@ class ElloBridge extends BridgeAbstract {
return parent::getName();
}
}

View File

@@ -15,7 +15,6 @@ class ExtremeDownloadBridge extends BridgeAbstract {
'filter' => array(
'name' => 'Type de contenu',
'type' => 'list',
'required' => 'true',
'title' => 'Type de contenu à suivre : Téléchargement, Streaming ou les deux',
'values' => array(
'Streaming et Téléchargement' => 'both',
@@ -100,5 +99,4 @@ class ExtremeDownloadBridge extends BridgeAbstract {
return $return;
}
}

View File

@@ -198,7 +198,6 @@ EOD;
}
//Builds the HTML from the encoded JS that Facebook provides.
private function buildContent($pageContent){
// The html ends with:
@@ -213,7 +212,6 @@ EOD;
return str_get_html($htmlContent);
}
//Builds the cookie from the page, as Facebook sometimes refuses to give
//the page if no cookie is provided.
private function getCookies($pageURL){
@@ -289,5 +287,4 @@ EOD;
public function getURI(){
return 'http://facebook.com';
}
}

View File

@@ -11,7 +11,6 @@ class FDroidBridge extends BridgeAbstract {
'u' => array(
'name' => 'Widget selection',
'type' => 'list',
'required' => true,
'values' => array(
'Latest added apps' => 'added',
'Latest updated apps' => 'updated'

View File

@@ -179,8 +179,7 @@ class FacebookBridge extends BridgeAbstract {
if(filter_var(
$group,
FILTER_VALIDATE_URL,
FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED)) {
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
// User provided a URL
$urlparts = parse_url($group);
@@ -687,7 +686,6 @@ EOD;
}
}
}
#endregion (User)
}

View File

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

View File

@@ -182,5 +182,4 @@ class FlickrBridge extends BridgeAbstract {
return $url;
}
}

View File

@@ -37,5 +37,4 @@ class ForGifsBridge extends FeedExpander {
return $item;
}
}

View File

@@ -10,7 +10,6 @@ class GBAtempBridge extends BridgeAbstract {
'type' => array(
'name' => 'Type',
'type' => 'list',
'required' => true,
'values' => array(
'News' => 'N',
'Reviews' => 'R',

View File

@@ -62,5 +62,4 @@ class GOGBridge extends BridgeAbstract {
return $content;
}
}

View File

@@ -9,7 +9,6 @@
*/
class GQMagazineBridge extends BridgeAbstract
{
const MAINTAINER = 'Riduidel';
const NAME = 'GQMagazine';
@@ -20,18 +19,18 @@ class GQMagazineBridge extends BridgeAbstract
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'GQMagazine section extractor bridge. This bridge allows you get only a specific section.';
const DEFAULT_DOMAIN = 'www.gqmagazine.fr';
const PARAMETERS = array( array(
'domain' => array(
'name' => 'Domain to use',
'required' => true,
'values' => array(
'www.gqmagazine.fr' => 'www.gqmagazine.fr'
),
'defaultValue' => 'www.gqmagazine.fr'
'defaultValue' => self::DEFAULT_DOMAIN
),
'page' => array(
'name' => 'Initial page to load',
'required' => true
'required' => true,
'exampleValue' => 'sexe/news'
),
));
@@ -42,7 +41,12 @@ class GQMagazineBridge extends BridgeAbstract
);
private function getDomain() {
return $this->getInput('domain');
$domain = $this->getInput('domain');
if (empty($domain))
$domain = self::DEFAULT_DOMAIN;
if (strpos($domain, '://') === false)
$domain = 'https://' . $domain;
return $domain;
}
public function getURI()

View File

@@ -160,5 +160,4 @@ EOD;
return $content;
}
}

View File

@@ -28,7 +28,7 @@ class GithubIssueBridge extends BridgeAbstract {
'i' => array(
'name' => 'Issue number',
'type' => 'number',
'required' => 'true'
'required' => true
)
)
);

View File

@@ -117,7 +117,7 @@ class GlassdoorBridge extends BridgeAbstract {
$item['title'] = $post->find('header', 0)->plaintext;
$item['content'] = $post->find('div[class="excerpt-content"]', 0)->plaintext;
$item['enclosures'] = array(
$this->getFullSizeImageURI($post->find('div[class="post-thumb"]', 0)->{'data-original'})
$this->getFullSizeImageURI($post->find('div[class*="post-thumb"]', 0)->{'data-original'})
);
// optionally load full articles
@@ -186,8 +186,7 @@ class GlassdoorBridge extends BridgeAbstract {
* redirection and strange naming conventions.
*/
if(!filter_var($uri,
FILTER_VALIDATE_URL,
FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED)) {
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
returnClientError('The specified URL is invalid!');
}

88
bridges/GlowficBridge.php Normal file
View File

@@ -0,0 +1,88 @@
<?php
class GlowficBridge extends BridgeAbstract {
const MAINTAINER = 'l1n';
const NAME = 'Glowfic Bridge';
const URI = 'https://www.glowfic.com';
const CACHE_TIMEOUT = 3600; // 1 hour
const DESCRIPTION = 'Returns the latest replies on a glowfic post.';
const PARAMETERS = array(
'global' => array(),
'Thread' => array(
'post_id' => array(
'name' => 'Post ID',
'title' => 'https://www.glowfic.com/posts/<POST ID>',
'type' => 'number'
),
'start_page' => array(
'name' => 'Start Page',
'title' => 'To start from an offset page',
'type' => 'number'
)
)
);
public function collectData() {
$url = $this->getAPIURI();
$metadata = get_headers( $url . '/replies', true ) or returnClientError('Post did not return reply headers.');
$metadata['Last-Page'] = ceil( $metadata['Total'] / $metadata['Per-Page'] );
if(!is_null($this->getInput('start_page')) &&
$this->getInput('start_page') < 1 && $metadata['Last-Page'] - $this->getInput('start_page') > 0) {
$first_page = $metadata['Last-Page'] - $this->getInput('start_page');
} else if(!is_null($this->getInput('start_page')) && $this->getInput('start_page') <= $metadata['Last-Page']) {
$first_page = $this->getInput('start_page');
} else {
$first_page = 1;
}
for ($page_offset = $first_page; $page_offset <= $metadata['Last-Page']; $page_offset++) {
$jsonContents = getContents($url . '/replies?page=' . $page_offset ) or
returnClientError('Could not retrieve replies for page ' . $page_offset . '.');
$replies = json_decode($jsonContents);
foreach ($replies as $reply) {
$item = array();
$item['content'] = $reply->{'content'};
$item['uri'] = $this->getURI() . '?page=' . $page_offset . '#reply-' . $reply->{'id'};
if ($reply->{'icon'}) {
$item['enclosures'] = array($reply->{'icon'}->{'url'});
}
$item['author'] = $reply->{'character'}->{'screenname'} . ' (' . $reply->{'character'}->{'name'} . ')';
$item['timestamp'] = date('r', strtotime($reply->{'created_at'}));
$item['title'] = 'Tag by ' . $reply->{'user'}->{'username'} . ' updated at ' . $reply->{'updated_at'};
$this->items[] = $item;
}
}
}
private function getAPIURI() {
$url = parent::getURI() . '/api/v1/posts/' . $this->getInput('post_id');
return $url;
}
public function getURI() {
$url = parent::getURI() . '/posts/' . $this->getInput('post_id');
return $url;
}
private function getPost() {
$url = $this->getAPIURI();
$jsonPost = getContents( $url ) or returnClientError('Could not retrieve post metadata.');
$post = json_decode($jsonPost);
return $post;
}
public function getName(){
if(!is_null($this->getInput('post_id'))) {
$post = $this->getPost();
return $post->{'subject'} . ' - ' . parent::getName();
}
return parent::getName();
}
public function getDescription(){
if(!is_null($this->getInput('post_id'))) {
$post = $this->getPost();
return $post->{'content'};
}
return parent::getName();
}
}

View File

@@ -205,5 +205,4 @@ class GooglePlusPostBridge extends BridgeAbstract{
return implode('', [$scheme, $user, $pass, $host, $port, $path, $query, $fragment]);
}
}

View File

@@ -17,27 +17,23 @@ class HotUKDealsBridge extends PepperBridgeAbstract {
'hide_expired' => array(
'name' => 'Hide expired deals',
'type' => 'checkbox',
'required' => 'true'
),
'hide_local' => array(
'name' => 'Hide local deals',
'type' => 'checkbox',
'title' => 'Hide deals in physical store',
'required' => 'true'
),
'priceFrom' => array(
'name' => 'Minimal Price',
'type' => 'text',
'title' => 'Minmal Price in Pounds',
'required' => 'false',
'defaultValue' => ''
'required' => false
),
'priceTo' => array(
'name' => 'Maximum Price',
'type' => 'text',
'title' => 'Maximum Price in Pounds',
'required' => 'false',
'defaultValue' => ''
'required' => false
),
),
@@ -45,7 +41,6 @@ class HotUKDealsBridge extends PepperBridgeAbstract {
'group' => array(
'name' => 'Group',
'type' => 'list',
'required' => 'true',
'title' => 'Group whose deals must be displayed',
'values' => array(
'2DS' => '2ds',
@@ -1319,7 +1314,6 @@ class HotUKDealsBridge extends PepperBridgeAbstract {
'order' => array(
'name' => 'Order by',
'type' => 'list',
'required' => 'true',
'title' => 'Sort order of deals',
'values' => array(
'From the most to the least hot deal' => '-hot',

View File

@@ -7,19 +7,19 @@ class InstagramBridge extends BridgeAbstract {
const DESCRIPTION = 'Returns the newest images';
const PARAMETERS = array(
array(
'Username' => array(
'u' => array(
'name' => 'username',
'required' => true
)
),
array(
'Hashtag' => array(
'h' => array(
'name' => 'hashtag',
'required' => true
)
),
array(
'Location' => array(
'l' => array(
'name' => 'location',
'required' => true
@@ -82,10 +82,20 @@ class InstagramBridge extends BridgeAbstract {
$item = array();
$item['uri'] = self::URI . 'p/' . $media->shortcode . '/';
if (isset($media->owner->username)) {
$item['author'] = $media->owner->username;
}
if (isset($media->edge_media_to_caption->edges[0]->node->text)) {
$item['title'] = $media->edge_media_to_caption->edges[0]->node->text;
$textContent = $media->edge_media_to_caption->edges[0]->node->text;
} else {
$item['title'] = basename($media->display_url);
$textContent = basename($media->display_url);
}
$item['title'] = ($media->is_video ? '▶ ' : '') . trim($textContent);
$titleLinePos = strpos(wordwrap($item['title'], 120), "\n");
if ($titleLinePos != false) {
$item['title'] = substr($item['title'], 0, $titleLinePos) . '...';
}
if(!is_null($this->getInput('u')) && $media->__typename == 'GraphSidecar') {
@@ -93,7 +103,9 @@ 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'] = '<a href="' . htmlentities($item['uri']) . '" target="_blank">';
$item['content'] .= '<img src="' . htmlentities($media->display_url) . '" alt="' . $item['title'] . '" />';
$item['content'] .= '</a><br><br>' . nl2br(htmlentities($textContent));
$item['enclosures'] = array($media->display_url);
}

View File

@@ -21,7 +21,6 @@ class InstructablesBridge extends BridgeAbstract {
'category' => array(
'name' => 'Category',
'type' => 'list',
'required' => true,
'values' => array(
'Play' => array(
'All' => '/play/',
@@ -240,7 +239,6 @@ class InstructablesBridge extends BridgeAbstract {
'filter' => array(
'name' => 'Filter',
'type' => 'list',
'required' => true,
'values' => array(
'Featured' => ' ',
'Recent' => 'recent/',

View File

@@ -34,7 +34,6 @@ class JustETFBridge extends BridgeAbstract {
'global' => array(
'lang' => array(
'name' => 'Language',
'required' => true,
'type' => 'list',
'values' => array(
'Englisch' => 'en',
@@ -348,6 +347,5 @@ class JustETFBridge extends BridgeAbstract {
return $element->plaintext;
}
#endregion
}

View File

@@ -53,6 +53,7 @@ class KATBridge extends BridgeAbstract {
$guessedDate['tm_year'] + 1900);
return $timestamp;
}
$catBool = $this->getInput('cat_check');
if($catBool) {
$catNum = $this->getInput('cat');

View File

@@ -150,5 +150,4 @@ class KernelBugTrackerBridge extends BridgeAbstract {
return $html;
}
}

View File

@@ -11,7 +11,6 @@ class KununuBridge extends BridgeAbstract {
'site' => array(
'name' => 'Site',
'type' => 'list',
'required' => true,
'title' => 'Select your site',
'values' => array(
'Austria' => 'at',
@@ -23,7 +22,6 @@ class KununuBridge extends BridgeAbstract {
'full' => array(
'name' => 'Load full article',
'type' => 'checkbox',
'required' => false,
'exampleValue' => 'checked',
'title' => 'Activate to load full article'
)

View File

@@ -419,7 +419,6 @@ class LeBonCoinBridge extends BridgeAbstract {
}
}
private function buildRequestJson() {
$requestJson = new StdClass();
@@ -534,5 +533,4 @@ class LeBonCoinBridge extends BridgeAbstract {
return json_encode($requestJson);
}
}

View File

@@ -20,12 +20,13 @@ class LeMondeInformatiqueBridge extends FeedExpander {
str_replace(
'/grande/',
'/petite/',
$article_html->find('.article-image', 0)->find('img', 0)->src
$article_html->find('.article-image > img, figure > img', 0)->src
)
);
//No response header sets the encoding, explicit conversion is needed or subsequent xml_encode() will fail
$item['content'] = utf8_encode($this->cleanArticle($article_html->find('div.col-primary', 0)->innertext));
$content_node = $article_html->find('div.col-primary, div.col-sm-9', 0);
$item['content'] = utf8_encode($this->cleanArticle($content_node->innertext));
$item['author'] = utf8_encode($article_html->find('div.author-infos', 0)->find('b', 0)->plaintext);
return $item;

View File

@@ -13,7 +13,6 @@ class MangareaderBridge extends BridgeAbstract {
'category' => array(
'name' => 'Category',
'type' => 'list',
'required' => true,
'values' => array(
'All' => 'all',
'Action' => 'action',

View File

@@ -0,0 +1,102 @@
<?php
class ModelKarteiBridge extends BridgeAbstract {
const NAME = 'model-kartei.de';
const URI = 'https://www.model-kartei.de/';
const DESCRIPTION = 'Get the public comp card gallery';
const MAINTAINER = 'fulmeek';
const PARAMETERS = array(array(
'model_id' => array(
'name' => 'Model ID',
'exampleValue' => '123456'
)
));
const LIMIT_ITEMS = 10;
private $feedName = '';
public function collectData() {
$model_id = preg_replace('/[^0-9]/', '', $this->getInput('model_id'));
if (empty($model_id))
returnServerError('Invalid model ID');
$html = getSimpleHTMLDOM(self::URI . 'sedcards/model/' . $model_id . '/')
or returnServerError('Model not found');
$objTitle = $html->find('.sTitle', 0);
if ($objTitle)
$this->feedName = $objTitle->plaintext;
$itemlist = $html->find('#photoList .photoPreview');
if (!$itemlist)
returnServerError('No gallery');
foreach($itemlist as $idx => $element) {
if ($idx >= self::LIMIT_ITEMS)
break;
$item = array();
$title = $element->title;
$date = $element->{'data-date'};
$author = $this->feedName;
$text = '';
$objImage = $element->find('a.photoLink img', 0);
$objLink = $element->find('a.photoLink', 0);
if ($objLink) {
$page = getSimpleHTMLDOMCached($objLink->href);
if (empty($title)) {
$objTitle = $page->find('.p-title', 0);
if ($objTitle)
$title = $objTitle->plaintext;
}
if (empty($date)) {
$objDate = $page->find('.cameraDetails .date', 0);
if ($objDate)
$date = strtotime($objDate->parent()->plaintext);
}
if (empty($author)) {
$objAuthor = $page->find('.p-publisher a', 0);
if ($objAuthor)
$author = $objAuthor->plaintext;
}
$objFullImage = $page->find('img#gofullscreen', 0);
if ($objFullImage)
$objImage = $objFullImage;
$objText = $page->find('.p-desc', 0);
if ($objText)
$text = $objText->plaintext;
}
$item['title'] = $title;
$item['timestamp'] = $date;
$item['author'] = $author;
if ($objImage)
$item['content'] = '<img src="' . $objImage->src . '"/>';
if ($objLink) {
$item['uri'] = $objLink->href;
if (!empty($item['content']))
$item['content'] = '<a href="' . $objLink->href . '" target="_blank">' . $item['content'] . '</a>';
} else {
$item['uri'] = 'urn:sha1:' . hash('sha1', $item['content']);
}
if (!empty($text))
$item['content'] = '<p>' . $text . '</p>' . $item['content'];
$this->items[] = $item;
}
}
public function getName(){
if(!empty($this->feedName)) {
return $this->feedName . ' - ' . self::NAME;
}
return parent::getName();
}
}

View File

@@ -21,7 +21,8 @@ class MozillaSecurityBridge extends BridgeAbstract {
$item['title'] = $element->innertext;
$item['timestamp'] = strtotime($element->innertext);
$item['content'] = $element->next_sibling()->innertext;
$item['uri'] = self::URI;
$item['uri'] = self::URI . '?' . $item['timestamp'];
$item['uid'] = self::URI . '?' . $item['timestamp'];
$this->items[] = $item;
}
}

View File

@@ -17,27 +17,23 @@ class MydealsBridge extends PepperBridgeAbstract {
'hide_expired' => array(
'name' => 'Abgelaufenes ausblenden',
'type' => 'checkbox',
'required' => 'true'
),
'hide_local' => array(
'name' => 'Lokales ausblenden',
'type' => 'checkbox',
'title' => 'Deals im physischen Geschäft ausblenden',
'required' => 'true'
),
'priceFrom' => array(
'name' => 'Minimaler Preis',
'type' => 'text',
'title' => 'Minmaler Preis in Euros',
'required' => 'false',
'defaultValue' => ''
'required' => false
),
'priceTo' => array(
'name' => 'Maximaler Preis',
'type' => 'text',
'title' => 'maximaler Preis in Euro',
'required' => 'false',
'defaultValue' => ''
'required' => false
),
),
@@ -45,7 +41,6 @@ class MydealsBridge extends PepperBridgeAbstract {
'group' => array(
'name' => 'Gruppen',
'type' => 'list',
'required' => 'true',
'title' => 'Gruppe, deren Deals angezeigt werden müssen',
'values' => array(
'Elektronik' => 'elektronik',
@@ -68,7 +63,6 @@ class MydealsBridge extends PepperBridgeAbstract {
'order' => array(
'name' => 'sortieren nach',
'type' => 'list',
'required' => 'true',
'title' => 'Sortierung der deals',
'values' => array(
'Vom heißesten zum kältesten Deal' => '',

37
bridges/N26Bridge.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
class N26Bridge extends BridgeAbstract
{
const MAINTAINER = 'quentinus95';
const NAME = 'N26 Blog';
const URI = 'https://n26.com';
const CACHE_TIMEOUT = 1800;
const DESCRIPTION = 'Returns recent blog posts from N26.';
public function getIcon()
{
return 'https://n26.com/favicon.ico';
}
public function collectData()
{
$html = getSimpleHTMLDOM(self::URI . '/en-fr/blog-archive')
or returnServerError('Error while downloading the website content');
foreach($html->find('div.ga') as $article) {
$item = [];
$item['uri'] = self::URI . $article->find('h2 a', 0)->href;
$item['title'] = $article->find('h2 a', 0)->plaintext;
$fullArticle = getSimpleHTMLDOM($item['uri'])
or returnServerError('Error while downloading the full article');
$dateElement = $fullArticle->find('span[class="fk fl de ch fm by"]', 0);
$item['timestamp'] = strtotime($dateElement->plaintext);
$item['content'] = $fullArticle->find('main article', 0)->innertext;
$this->items[] = $item;
}
}
}

View File

@@ -11,7 +11,6 @@ class NineGagBridge extends BridgeAbstract {
'd' => array(
'name' => 'Section',
'type' => 'list',
'required' => true,
'values' => array(
'Hot' => 'hot',
'Trending' => 'trending',
@@ -28,7 +27,6 @@ class NineGagBridge extends BridgeAbstract {
'g' => array(
'name' => 'Section',
'type' => 'list',
'required' => true,
'values' => array(
'Animals' => 'cute',
'Anime & Manga' => 'anime-manga',
@@ -88,7 +86,6 @@ class NineGagBridge extends BridgeAbstract {
't' => array(
'name' => 'Type',
'type' => 'list',
'required' => true,
'values' => array(
'Hot' => 'hot',
'Fresh' => 'fresh',

View File

@@ -21,8 +21,7 @@ class NotAlwaysBridge extends BridgeAbstract {
'Friendly' => 'friendly',
'Hopeless' => 'hopeless',
'Unfiltered' => 'unfiltered'
),
'required' => true
)
)
));

View File

@@ -9,7 +9,6 @@ class OnVaSortirBridge extends FeedExpander {
'city' => array(
'name' => 'City',
'type' => 'list',
'required' => true,
'values' => array(
'Agen' => 'Agen',
'Ajaccio' => 'Ajaccio',
@@ -111,11 +110,11 @@ class OnVaSortirBridge extends FeedExpander {
'Valence' => 'valence',
'Vannes' => 'vannes',
'Zurich' => 'zurich',
),
'defaultValue' => ''
)
)
)
);
protected function parseItem($item){
$item = parent::parseItem($item);
$html = getSimpleHTMLDOMCached($item['uri']);
@@ -123,6 +122,7 @@ class OnVaSortirBridge extends FeedExpander {
$item['content'] = utf8_encode($text);
return $item;
}
public function collectData(){
$this->collectExpandableDatas('https://' .
$this->getInput('city') . '.onvasortir.com/rss.php');

View File

@@ -0,0 +1,962 @@
<?php
class OneFortuneADayBridge extends BridgeAbstract {
const NAME = 'One Fortune a Day';
const URI = 'https://github.com/fulmeek';
const DESCRIPTION = 'Get a fortune quote every single day.';
const MAINTAINER = 'fulmeek';
const PARAMETERS = array(array(
'time' => array(
'name' => 'Time in UTC',
'type' => 'list',
'values' => array(
'0:00' => 0,
'1:00' => 1,
'2:00' => 2,
'3:00' => 3,
'4:00' => 4,
'5:00' => 5,
'6:00' => 6,
'7:00' => 7,
'8:00' => 8,
'9:00' => 9,
'10:00' => 10,
'11:00' => 11,
'12:00' => 12,
'13:00' => 13,
'14:00' => 14,
'15:00' => 15,
'16:00' => 16,
'17:00' => 17,
'18:00' => 18,
'19:00' => 19,
'20:00' => 20,
'21:00' => 21,
'22:00' => 22,
'23:00' => 23,
),
'defaultValue' => 5
),
'lucky' => array(
'name' => 'Lucky number (optional)',
'type' => 'text'
)
));
const LIMIT_ITEMS = 7;
const DAY_SECS = 86400;
public function getDescription(){
return self::DESCRIPTION . '<br/>Set a lucky number to get your personal quotes, like ' . mt_rand();
}
public function collectData() {
$time = gmmktime((int)$this->getInput('time'), 0, 0);
if ($time > time())
$time -= self::DAY_SECS;
for ($i = self::LIMIT_ITEMS; $i > 0; --$i) {
$seed = gmdate('Ymd', $time) . $this->getInput('lucky');
$quote = $this->getQuote($seed);
$item['title'] = strftime('%A, %x', $time);
$item['content'] = htmlentities($quote, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = $time;
$item['uid'] = hash('sha1', $seed);
$this->items[] = $item;
$time -= self::DAY_SECS;
}
}
private function getQuote($seed) {
$quotes = explode('//', <<<QUOTES
People are naturally attracted to you.
//You learn from your mistakes... You will learn a lot today.
//If you have something good in your life, don't let it go!
//What ever you're goal is in life, embrace it visualize it, and for it will be
yours.
//Your shoes will make you happy today.
//You cannot love life until you live the life you love.
//Be on the lookout for coming events; They cast their shadows beforehand.
//Land is always on the mind of a flying bird.
//The man or woman you desire feels the same about you.
//Meeting adversity well is the source of your strength.
//A dream you have will come true.
//Our deeds determine us, as much as we determine our deeds.
//Never give up. You're not a failure if you don't give up.
//You will become great if you believe in yourself.
//There is no greater pleasure than seeing your loved ones prosper.
//You will marry your lover.
//A very attractive person has a message for you.
//You already know the answer to the questions lingering inside your head.
//It is now, and in this world, that we must live.
//You must try, or hate yourself for not trying.
//You can make your own happiness.
//The greatest risk is not taking one.
//The love of your life is stepping into your planet this summer.
//Love can last a lifetime, if you want it to.
//Adversity is the parent of virtue.
//Serious trouble will bypass you.
//A short stranger will soon enter your life with blessings to share.
//Now is the time to try something new.
//Wealth awaits you very soon.
//If you feel you are right, stand firmly by your convictions.
//If winter comes, can spring be far behind?
//Keep your eye out for someone special.
//You are very talented in many ways.
//A stranger, is a friend you have not spoken to yet.
//A new voyage will fill your life with untold memories.
//You will travel to many exotic places in your lifetime.
//Your ability for accomplishment will follow with success.
//Nothing astonishes men so much as common sense and plain dealing.
//Its amazing how much good you can do if you dont care who gets the credit.
//Everyone agrees. You are the best.
//LIFE CONSIST NOT IN HOLDING GOOD CARDS, BUT IN PLAYING THOSE YOU HOLD WELL.
//Jealousy doesn't open doors, it closes them!
//It's better to be alone sometimes.
//When fear hurts you, conquer it and defeat it!
//Let the deeds speak.
//You will be called in to fulfill a position of high honor and responsibility.
//The man on the top of the mountain did not fall there.
//You will conquer obstacles to achieve success.
//Joys are often the shadows, cast by sorrows.
//Fortune favors the brave.
//An upward movement initiated in time can counteract fate.
//A journey of a thousand miles begins with a single step.
//Sometimes you just need to lay on the floor.
//Never give up. Always find a reason to keep trying.
//If you have something worth fighting for, then fight for it.
//Stop wishing. Start doing.
//Accept your past without regrets. Handle your present with confidence. Face
your future without fear.
//Stay true to those who would do the same for you.
//Ask yourself if what you are doing today is getting you closer to where you
want to be tomorrow.
//Happiness is an activity.
//Help is always needed but not always appreciated. Stay true to your heart and
help those in need weather they appreciate it or not.
//Hone your competitive instincts.
//Finish your work on hand don't be greedy.
//For success today, look first to yourself.
//Your fortune is as sweet as a cookie.
//Integrity is the essence of everything successful.
//If you're happy, you're successful.
//You will always be surrounded by true friends
//Believing that you are beautiful will make you appear beautiful to others
around you.
//Happinees comes from a good life.
//Before trying to please others think of what makes you happy.
//When hungry, order more Chinese food.
//Your golden opportunity is coming shortly.
//For hate is never conquered by hate. Hate is conquered by love .
//You will make many changes before settling down happily.
//A man is born to live and not prepare to live.
//You cannot become rich except by enriching others.
//Don't pursue happiness - create it.
//You will be successful in love.
//All your fingers can't be of the same length.
//Wise sayings often fall on barren ground, but a kind word is never thrown away.
//A lifetime of happiness is in store for you.
//It is very possible that you will achieve greatness in your lifetime.
//Be tactful; overlook your own opportunity.
//You are the controller of your destiny.
//Everything happens for a reson.
//How can you have a beutiful ending without making beautiful mistakes.
//You can open doors with your charm and patience.
//Welcome the change coming into your life.
//There will be a happy romance for you shortly.
//Your fondest dream will come true within this year.
//You have a deep interest in all that is artistic.
//Your emotional nature is strong and sensitive.
//A letter of great importance may reach you any day now.
//Good health will be yours for a long time.
//You will become better acquainted with a coworker.
//To be old and wise, you must first be young and stupid.
//Failure is only the opportunity to begin again more intelligently.
//Integrity is doing the right thing, even if nobody is watching.
//Conquer your fears or they will conquer you.
//You are a lover of words; One day you will write a book.
//In this life it is not what we take up, but what we give up, that makes us
rich.
//Fear can keep us up all night long, but faith makes one fine pillow.
//Seek out the significance of your problem at this time. Try to understand.
//Never upset the driver of the car you're in; they're the master of your
destiny until you get home.
//He who slithers among the ground is not always a foe.
//You learn from your mistakes, you will learn a lot today.
//You only need look to your own reflection for inspiration. Because you are
Beautiful!
//You are not judged by your efforts you put in; you are judged on your
performance.
//Rivers need springs.
//Good news from afar may bring you a welcome visitor.
//When all else seems to fail, smile for today and just love someone.
//Patience is a virtue, unless its against a brick wall.
//When you look down, all you see is dirt, so keep looking up.
//If you are afraid to shake the dice, you will never throw a six.
//Even if the person who appears most wrong, is also quite often right.
//A single conversation with a wise man is better than ten years of study.
//Happiness is often a rebound from hard work.
//The world may be your oyster, but that doesn't mean you'll get it's pearl.
//Your life will be filled with magical moments.
//You're true love will show himself to you under the moonlight.
//Do not follow where the path may lead. Go where there is no path...and leave a
trail
//Do not fear what you don't know
//The object of your desire comes closer.
//You have a flair for adding a fanciful dimension to any story.
//If you wish to know the mind of a man, listen to his words
//The most useless energy is trying to change what and who God so carefully
created.
//Do not be covered in sadness or be fooled in happiness they both must exist
//You will have unexpected great good luck.
//You will have a pleasant surprise
//All progress occurs because people dare to be different.
//Your ability for accomplishment will be followed by success.
//The world is always ready to receive talent with open arms.
//Things may come to those who wait, but only the things left by those who
hustle.
//We can't help everyone. But everyone can help someone.
//Every day is a new day. But tomorrow is never promised.
//Express yourself: Don't hold back!
//It is not necessary to show others you have change; the change will be obvious.
//You have a deep appreciation of the arts and music.
//If your desires are not extravagant, they will be rewarded.
//You try hard, never to fail. You don't, never to win.
//Never give up on someone that you don't go a day without thinking about.
//It never pays to kick a skunk.
//In case of fire, keep calm, pay bill and run.
//Next full moon brings an enchanting evening.
//Not all closed eye is sleeping nor open eye is seeing.
//Impossible is a word only to be found in the dictionary of fools.
//You will soon witness a miracle.
//The time is alway right to do what is right.
//Love is as necessary to human beings as food and shelter.
//You will make heads turn.
//You are extremely loved. Don't worry :)
//If you are never patient, you will never get anything done. If you believe you
can do it, you will be rewarded with success.
//You will soon embark on a business venture.
//You believe in the goodness of man kind.
//You will have a long and wealthy life.
//You will take a pleasant journey to a place far away.
//You are a person of culture.
//Keep it simple. The more you say, the less people remember.
//Life is like a dogsled team. If you ain't the lead dog, the scenery never
changes.
//Prosperity makes friends and adversity tries them.
//Nothing seems impossible to you.
//Patience is bitter, but its fruit is sweet.
//The only certainty is that nothing is certain.
//Success is the sum of my unique visions realized by the sweat of perseverance.
//When you expect your opponent to yield, you also should avoid hurting him.
//Human evolution: “wider freeway but narrower viewpoints.
//Intelligence is the door to freedom and alert attention is the mother of
intelligence.
//Back away from individuals who are impulsive.
//Enjoyed the meal? Buy one to go too.
//You believe in the goodness of mankind.
//A big fortune will descend upon you this year.
//Now these three remain, faith, hope, and love. The greatest of these is love.
//For success today look first to yourself.
//Determination is the wake-up call to the human will.
//There are no limitations to the mind except those we aknowledge.
//A merry heart does good like a medicine.
//Whenever possible, keep it simple.
//Your dearest wish will come true.
//Poverty is no disgrace.
//If you dont do it excellently, dont do it at all.
//You have an unusual equipment for success, use it properly.
//Emotion is energy in motion.
//You will soon be honored by someone you respect.
//Punctuality is the politeness of kings and the duty of gentle people
everywhere.
//Your happiness is intertwined with your outlook on life.
//Elegant surroundings will soon be yours.
//If you feel you are right, stand firmly by your convictions.
//Your smile brings happiness to everyone you meet.
//Instead of worrying and agonizing, move ahead constructively.
//Do you believe? Endurance and persistence will be rewarded.
//A new business venture is on the horizon.
//Never underestimate the power of the human touch.
//Hold on to the past but eventually, let the times go and keep the memories
into the present.
//Truth is an unpopular subject. Because it is unquestionably correct.
//The most important thing in communication is to hear what isnt being said.
//You are broad minded and socially active.
//Your dearest dream is coming true. God looks after you especially.
//You will recieve some high prize or award.
//Your present question marks are going to succeed.
//You have a fine capacity for the enjoyment of life.
//You will live long and enjoy life.
//An admirer is concealing his/her affection for you.
//A wish is what makes life happen when you dream of rose petals.
//Love can turn cottage into a golden palace.
//Lend your money and lose your freind.
//You will kiss your crush ohhh lalahh
//You will be rewarded for being a good listener in the next week.
//If you never give up on love, It will never give up on you.
//Unleash your life force.
//Your wish will come true.
//There is a prospect of a thrilling time ahead for you.
//No distance is too far, if two hearts are tied together.
//Land is always in the mind of the flying birds.
//Try? No! Do or do not, there is no try.
//Do not worry, you will have great peace.
//It's about time you asked that special someone on a date.
//You create your own stage ... the audience is waiting.
//It is never too late. Just as it is never too early.
//Discover the power within yourself.
//Good things take time.
//Stop thinking about the road not taken and pave over the one you did.
//Put your unhappiness aside. Life is beautiful, be happy.
//You can still love what you can not have in life.
//Make a wise choice everyday.
//Circumstance does not make the man; it reveals him to himself.
//The man who waits till tomorrow, misses the opportunities of today.
//Life does not get better by chance. It gets better by change.
//If you never expect anything you can never be disappointed.
//People in your surroundings will be more cooperative than usual.
//True wisdom is found in happiness.
//Ones always regrets what could have done. Remember for next time.
//Follow your bliss and the Universe will open doors where there were once only
walls.
//Find a peaceful place where you can make plans for the future.
//All the water in the world can't sink a ship unless it gets inside.
//The earth is a school learn in it.
//In music, one must think with his heart and feel with his brain.
//If you speak honestly, everyone will listen.
//Ganerosity will repay itself sooner than you imagine.
//good things take time
//Do what is right, not what you should.
//To effect the quality of the day is no small achievement.
//Simplicity and clearity should be the theme in your dress.
//Virtuous find joy while Wrongdoers find grieve in their actions.
//Not all closed eye is sleeping, nor open eye is seeing.
//Bread today is better than cake tomorrow.
//In evrything there is a piece of truth.But a piece.
//A feeling is an idea with roots.
//Man is born to live and not prepare to live
//It's all right to have butterflies in your stomach. Just get them to fly in
formation.
//If you don t give something, you will not get anything
//The harder you try to not be like your parents, the more likely you will
become them
//Someday everything will all make perfect sense
//you will think for yourself when you stop letting others think for you
//Everything will be ok. Don't obsess. Time will prove you right, you must stay
where you are.
//Let's finish this up now, someone is waiting for you on that
//The finest men like the finest steels have been tempered in the hottest
furnace.
//A dream you have will come true
//The worst of friends may become the best of enemies, but you will always find
yourself hanging on.
//I think, you ate your fortune while you were eating your cookie
//If u love someone keep fighting for them
//Do what you want, when you want, and you will be rewarded
//Let your fantasies unwind...
//The cooler you think you are the dumber you look
//Expect great things and great things will come
//The Wheel of Good Fortune is finally turning in your direction!
//Don't lead if you won't lead.
//You will always be successful in your professional career
//Share your hapiness with others today.
//It's up to you to clearify.
//Your future will be happy and productive.
//Seize every second of your life and savor it.
//Those who walk in other's tracks leave no footprints.
//Failure is the mother of all success.
//Difficulty at the beginning useually means ease at the end.
//Do not seek so much to find the answer as much as to understand the question
better.
//Your way of doing what other people do their way is what makes you special.
//A beautiful, smart, and loving person will be coming into your life.
//Friendship is an ocean that you cannot see bottom.
//Your life does not get better by chance, it gets better by change.
//Our duty,as men and women,is to proceed as if limits to our ability did not
exist.
//A pleasant expeience is ahead:don't pass it by.
//Our perception and attitude toward any situation will determine the outcome
//They say you are stubborn; you call it persistence.
//Two small jumps are sometimes better than one big leap.
//A new wardrobe brings great joy and change to your life.
//The cure for grief is motion.
//It's a good thing that life is not as serious as it seems to the waiter
//I hear and I forget. I see and I remember. I do and I understand.
//I have a dream....Time to go to bed.
//Ideas you believe are absurd ultimately lead to success!
//A human being is a deciding being.
//Today is an ideal time to water your parsonal garden.
//Some men dream of fortunes, others dream of cookies.
//Things are never quite the way they seem.
//the project on your mind will soon gain momentum
//YOUR FAILURES WILL LEAD YOU TO YOUR SUCCESS.
//IN ORDER TO GET THE RAINBOW, YOU MUST ENDURE THE RAIN.
//Beauty is simply beauty. originality is magical.
//Your dream will come true when you least expect it.
//Let not your hand be stretched out to receive and shut when you should repay.
//Don't worry, half the people you know are below average.
//Vision is the art of seeing what is invisible to others.
//You don't need talent to gain experience.
//A focused mind is one of the most powerful forces in the universe.
//Today you shed your last tear. Tomorrow fortune knocks at your door.
//Be patient! The Great Wall didn't got build in one day.
//Think you can. Think you can't. Either way, you'll be right.
//Wisdom is on her way to you.
//Digital circuits are made from analog parts.
//If you eat a box of fortune cookies, anything is possible.
//The best is yet to come.
//I'm with you.
//Be direct,usually one can accomplish more that way.
//A single kind work will keep one warm for years.
//Ask a friend to join you on your next voyage.
//In God we trust.
//Love is free. Lust will cost you everything you have.
//Stop searching forever, happiness is just next to you.
//You don't need the answers to all of life's questions. Just ask your father
what to do.
//Jealousy is a useless emotion.
//You are not a ghost.
//There is someone rather annoying in your life that you need to listen to.
//You will plant the smallest seed and it will become the greatest and most
mighty tree in the world.
//The dream you've been dreaming all your life isn't worth it. Find a new dream,
and once you're sure you've found it, fight for it.
//See if you can learn anything from the children.
//It's Never Too Late For Good Things To Happen!
//A clear conscience is usually the sign of a bad memory.
//Aim high, time flies.
//One is not sleeping, does not mean they are awake.
//A great pleasure in life is doing what others say you can't.
//Isn't there something else you should be working on right now?
//Your father still loves and is in always with you. Remember that.
//Before you can be reborn you must die.
//It better to be the hammer than the nail.
//You are admired by everyone for your talent and ability.
//Save the whales. Collect the whole set.
//You will soon discover a major truth about the one you love most.
//Your life will prosper only if you acknowledge your faults and work to reduce
them.
//Pray to God, but row towards shore.
//You will soon witness a miracle.
//The early bird gets the worm, but the second mouse gets the cheese
//Help, I'm being held prisoner in a Chinese cookie factory.
//Alas! The onion you are eating is someone elses water lily.
//You are a persoon with a good sense of justice, now it's time to act like it.
//You create enthusiasm around you.
//There are big changes ahead for you. They will be good ones!
//You will have many happy days soon.
//Out of confusion comes new patterns.
//If you love someone enough and they break your heart, you can't stop yourself
from still loving them again even after all that pain.
//Look right...Now look left...Now look forward (do this really fast) do you
feel any different? good you should feel dizzy.
//Live like you are on the bottom, even if you are on the top.
//You will soon emerge victorious from the maze you've been traveling in.
//Do not judge a book by it's color.
//Everything will come your way.
//There is a time to be practical now.
//Bend the rod while it is still hot.
//Darkness is only succesful when there is no light. Don't forget about light!
//Acting is not lying. It is findind someone hiding inside you and letting that
person run free.
//You will be forced to face fear, but if you do not run, fear will be afraid of
you.
//You are thinking about doing something. Don't do it, it won't help anything.
//Your worst enemy has a crush on you!
//Love Conquers all.
//The phrase is follow your dreams. Not dream period.
//stop nagging to your partner and take it day by day.
//Do not think that me or my brothers have supreme control over what will happen
to you.
//Bad luck and misfortune will follow you all your days.
//Remember the fate of the early Worm.
//Begin your life anew with strength, grace and wonder.
//Be a good friend and a fair enemy.
//What goes around comes around.
//Bad luck and misfortune will infest your pathetic soul for all eternity.
//The best prophet of the future is the past
//Movies have pause buttons, friends do not
//Use the force.
//Trust your intuition.
//Encourage your peers.
//Let your imagination wander.
//Your pain is the breaking of the shell that encloses your understanding.
//Patience is key, a wait short or long will have its reward.
//Tell them before it's too late...
//A bird in the hand is worth three in the bush!!
//Be assertive when decisive action is needed.
//To determine whether someone is beautiful is not by looking at his/her
appearance, but his/her heart.
//Hope brings about a better future
//While you have this day, fill it with life. While you're in this moment, give
it your own special meaning and purpose and joy.
//Even though it will often be difficult and complicated, you know you have what
it takes to get it done.
//You can choose, right now and in every moment, to put your powerful and
effective abilities to purposeful use. There is always something you can do, no
matter what the situation may be, that will move your life forward.
//IT IS NOT GOOD TO BE A USER BLESSINGS COME FROM BEING A GIVER NOT A TAKER.
//Cookie says, You crack me up
//You will prosper in the field of wacky inventions.
//Your tongue is your ambassador.
//The cure for grief is movement.
//Love Is At Your Hands Be Glad And Hold On To It.
//You are often asked if it is in yet.
//Life to you is a bold and dashing responsibility.
//Patience is a key to joy.
//A bargain is something you don't need at a price you can't resist.
//Today is going to be a disasterous day, be prepared!
//Stay to your inner-self, you will benefit in many ways.
//Rarely do great beauty and great virtue dwell together as they do in you.
//You are talented in many ways.
//You are the master of every situation.
//Your problem just got bigger. Think, what have you done.
//If your cookie still in one piece, buy lotto.
//Go with the flow will make your transition ever so much easier.
//Tomorrow Morning,Take a Left Turn As Soon As You Leave Home
//A metaphor could save your life.
//Don't wait for your ship to come in, swim out to it
//There are lessons to be learned by listening to others.
//If you want the rainbow, you have to tolerate the rain.
//Volition, Strength, Languages, Freedom and Power rests in you.
//TOO MANY PEOPLE VOLUNTEER TO CARRY THE STOOL WHEN ITS TIME TO MOVE THE PIANO
//It takes more than a good memory to have good memories.
//You are what you are; understand yourself before you react
//Word to the wise: Don't play leapfrog with a unicorn.........
//Forgive your enemies, but never forget them.
//Everything will now come your way
//Don't worry about the stock market. Invest in family.
//Your fortune is as sweet as a cookie.
//It is much easier to look for the bad, than it is to find the good
//If a person who has caused you pain and suffering has brought you, reconsider
that person's value in your life
//You are worth loving, you are also worth the effort it takes to love you
//Never trouble trouble till trouble troubles you.
//Get off to a new start - come out of your shell.
//Life is a dancefloor,you are the DJ!
//Cooperate with those who have both know how and integrith.
//Minor aches today are likely to pay off handsomely tomorrow.
//You are about to become $8.95 poorer. ($6.95 if you had the buffet)
//Your mouth may be moving, but nobody is listening.
//Focus in on the color yellow tomorrow for good luck!
//The problem with resisting temptation is that it may never come again.
//All your sorrows will vanish.
//About time I got out of that cookie.
//Love will lead the way.
//The ads revenge is massive success
//It is best to act with confidence, no matter how little right you have to it.
//Soon, a visitor shall delight you.
//What breaks in a moment may take years to mend.
//Someone stole your fortune and replaced it with this one. Your luck sucks.
Have a good day!
//Take control of your life rather than letting things happen just like that!
//You will be rewarded for your patience and understanding.
//You will achieve all your desires and pleasures.
//Never miss a chance to keep your mouth shut.
//Nothing Shows A Man's Character More Than What He Laughs At.
//Never regret anything that made you smile.
//Love Takes Pratice.
//Don't take yourself so seriously, no one else does.
//You've got what it takes, but it will take everything you've got!
//At this very moment you can change the rest of your life.
//Become who you are.
//All comes at the proper time to him who knows how to wait.
//The energy is within you. Money is Coming!
//The quotes that you do not understand, are not meant for you.
//You have an important new business development shaping up.
//if love someone a lot tell it before it's too late
//Birds are entangled by their feet and men by their tongues.
//Benefit by doing things that others give up on.
//Rest has a peaceful effect on your physical and emotional health.
//One of the best ways to persuade others is with your ears--by listening to
them.
//Plan your work and work your plan.
//Over self-confidence is equal to being blind.
//Those who bring sunshine to the lives of others cannot keep it from themselves.
//Love or money, or neither?
//Before the beginning of great brilliance, there must be chaos.
//Old friends make best friends.
//Stop searching forever. Happiness is just next to you.
//Accept something that you cannot change, and you will feel better.
//Kiss is not a kiss without the heart.
//Enhance your karma by engaging in various charitable activities.
//You will have good luck and overcome many hardships.
//You never hesitate to tackle the most difficult problems.
//Hope is like food. You will starve without it.
//WHEN FIRE AND WATER GO TO WAR WATER ALWAYS WINS.
//An angry man opens his mouth and shuts up his eyes.
//Make the system work for you, not the other way around.
//You will be hungry soon, order takeout now.
//Be prepared for extra energy.
//An unexpected relationship will become permanent.
//The love of your life is sitting across from you.
//Better be the head of a chicken than the tail of an ox.
//To forgive others one more time is to create one more blessing for yourself.
//Enjoy yourself while you can.
//The ultimate test of a relationship is to disagree but to hold hands.
//Excellence is the difference between what I do and what I am capable of.
//Do not let what you do not have prevent you from using what you do have.
//What ends on hope does not end at all.
//People enjoy having you around. Appreciate this.
//You are admired for your adventuous ways.
//It's never crowded along the extra mile
//You are blessed, today is the day to bless others.
//The Greatest War Sometimes Isn't On The Battlefield But Against Oneself.
//People in your background will be more co-operative than usual.
//A good way to stay healthy is to eat more Chinese food.
//Anyone who dares to be, can never be weak.
//Affirm it, visualize it, believe it, and it`will actualize itself.
//The measure of time to your next goal is the measure of your discipline.
//Help, I'm prisoner in a Chinese bakery!!!
//Take a minute and let it ride, then take a minute to let it breeze.
//We are here to love each other, serve each other and uplift each other.
//If everybody is a worm you should be a glow worm
//To affirm is to make firm.
//Remember this: duct tape can fix anything, so don't worry about messing things
up.
//You broke my cookie!
//Failure is not defeat until you stop trying.
//The days that make us happy make us wise.
//Men do not fail... they give up trying.
//Time may fly by. But Memories don't.
//You will win success in whatever you adopt.
//You will outdistance all your competitors.
//You have a great capability to break cookies - use it wisely!
//AT TIMES IT IS BETTER TO KNOW WHEN EXIT THAN ENTER
//Money will come to you when you are doing the right thing.
//When you get something for nothing, you just haven't been billed for it yet.
//You will discover your hidden talents.
//You'll advance for with your abilities.
//When you can't naturally feel upbeat it can sometimes help you to act as if
you did.
//You will overcome difficult times.
//Your problem just became your stepping stone. Catch the moment.
//I am a fortune. You just broke my little house. Where will i live now?
//The majority of the word can't is can.
//The secret of getting ahead is getting started.
//Be most affectionate today.
//Change your thoughts and you change the world.
//Sing and rejoice, fortune is smiling on you.
//All the preparation you've done will finally be paying off!
//A truly great person never puts away the simplicity of a child.
//Customer service is like taking a bath you have to keep doing it.
//The expanse of your intelligence is a void no universe could ever fill.
//Those grapes you cannot taste are always sour.
//An unexpected aquaintance will resurface.
//If you want the rainbow, then you have to tolerate the rain.
//You don't get harmony when everyone sings the same note.
//The race is not always to the swift, but to those who keep on running.
//The early bird gets the worm, but the second mouse gets the cheese.
//The best things in life aren't things.
//Don't bother looking for fault. The reward for finding it is low.
//Everything has beauty but not everyone sees it.
//Nothing is as good or bad as it appears.
//Never cut what you can untie.
//Meet your opponent half way. You need the exercise.
//Laughter is the shortest distance between two people.
//We cannot change the direction of the wind, but we can adjust our sails.
//We could learn a lot from crayons: Some of are sharp, some are pretty, some
have weird names, and all are different colors. But they all have to learn to
live in the same box.
//Use your instincts now.
//If you take a single step to your journey, you'll succeed; it's not best to
fail.
//In the eyes of lovers, everything is beautiful.
//Warning, do not eat your fortune.
//Demonstrate refinement in everything you do.
//Impossible standards just make life difficult.
//A different world cannot be build by indifferent people.
//Q. What is H2O? A. Caring, 2 parts Hug and 1 part Open-mind.
//All troubles you have can pass away very quickly.
//Integrity is the essense of everything successful.
//For true love? Send real roses preserved in 24kt gold!
//Sometimes the object of the journey is not the end, but the journey itself.
//Fear is just excitement in need of an attitude adjustment.
//The food here taste so good, even a cave man likes it.
//Perhaps you've been focusing too much on spending.
//Happiness isn't something you remember, it's something you experience.
//Oops... Wrong cookie.
//The dream is within you.
//Love is on its way.
//Be direct, usually one can accomplish more that way.
//Use your talents. That's what they are intended for.
//The troubles you have now will pass away quickly.
//See the light at the end of the tunnel.
//Your dream will come true when you least expect it.
//Don't 'face' reality, let it be the place from which you leap.
//Fortune smiles upon you today.
//Believing is doing.
//Your dynamic eyes have attracted a secret admirer.
//You know where you are going and how to get there.
//Go confidently in the direction of your dreams.
//Your ability to pick a winner will bring you success.
//Humor usually works at the moment of awkwardness.
//A good time to finish up old tasks.
//Stop procrastinating - starting tomorrow
//Enthusiastic leadership gets you a promotion when you least expect it.
//You love Chinese food.
//You are far more influential than you think.
//Adjust finances, make budgets, to improve your standing.
//Happiness is not the absence of conflict, but the ability to cope with it.
//An understanding heart warms all that are graced with it's presense.
//Your co-workers take pleasure in your great sense of creativity.
//You are one of the people who goes places in life.
//Others enjoy your company.
//When in doubt, let your instincts guide you.
//A cheerful message is on its way to you.
//A pleasant surprise is in store for you tonight.
//you cant go down the right path with out first discovering the path to go down
//To courageously shoulder the responsibility of one's mistake is character.
//The joyful energy of the day will have a positive affect on you.
//You have a strong desire for a home and your family interests come first.
//Dogs have owners, cats have staff.
//Be patient: in time, even an egg will walk.
//You are not a person who can be ignored.
//You always know the right times to be assertive or to simply wait.
//Reading to the mind is what exercise is to the body.
//Eat something you never tried before.
//Your life becomes more and more of an adventure!
//You need to live authentically, and you can't ignore that.
//Make all you can, save all you can, give all you can.
//A well-aimed spear is worth three.
//To build a better world, start in your community.
//When you can't naturally feel upbeat, it can sometimes help to act a if you
did.
//May you have great luck.
//A kind word will keep someone warm for years.
//Nothing in the world is accomplished without passion.
//Human invented language to satisfy the need to complain.
//Accept what comes to you each day.
//A small lucky package is on its way to you soon.
//In human endeavor, chance favors the prepared mind.
//Do not upset the penguin today.
//Don't cry.
//The best way to give credit is to give it away.
//Anything you do, do it well. The last thing you want is to be sorry for what
you didn't do.
//It takes more then good memory to have good memories.
//Grant yourself a wish this year only you can do it.
//love thy neighbour, just don't get caught
//You will be selected for a promotion because of your accomplishments.
//There are many new opportunities that are being presented to you.
//You will inherit a large sum of money.
//You will recieve a gift from someone that cares about you.
//You are not illiterate.
//Love because it is the only true adventure.
//You are contemplating some action which will bring credit upon you
//Keep true to the dreams of your youth.
//Treasure what you have.
//The greatest precept is continual awareness.
//A new friend helps you break out of an old routine.
//I have a dream.... Time to go to bed.
//Your skill will accomplish what the force of many cannot.
//You will soon be surrounded by good friends and laughter.
//The best is yet to come.
//It is better to be the hammer then the anvil.
//He who climbs a ladder must begin at the first step.
//Action speaks nothing, without the Motive.
//Give yourself some peace and quiet for at least a few hours.
//Live each day well and wisely
//Old dreams never die they just get filed away.
//You can fix it with a little extra energy and a positive attitude.
//Life is a verb
//A man without aim is like a clock without hands, as useless if it turns as if
it stands.
//Many folks are about as happy as they make up their minds to be.
//It's kind of fun to do the impossible
//Wow! A secret message from you teeth!
//You should be able to make money and hold on to it.
//The human spirit is stronger than anything that can happen to it.
//Your succeess will astonish everyone.
//It is better to have a hen tomorrow than an egg today.
//Judge each day not by the harvest you reap but by the seeds you plant.
//You like Chinese food.
//Your hard work will get payoff today.
//Today is the tomorrow we worried about yesterday
//There are no shortcuts to any place worth going
//No matter what your past has been, you have a spotless future.
//Your secret desire to completely change your life will manifest.
//Soon you will be sitting on top of the world.
//You are never selfish with your advice or your help.
//A thrilling time is in store for you.
//It's tough to be fascinating.
//Soon life will become more interesting
//Luck sometimes visits a fool, but it never sits down with him.
//Keep your plans secret for now.
//Aren't you glad you just had a great meal?
//Traveling this year will bring your life into greater perspective.
//Only talent people get help from others.
//Constant grinding can turn an iron nod into a needle.
//You will be successful in your work
//you will spend old age in confort and material wealth
//When you're about to turn your heart into a stone remember: you do not walk
alone.
//I am a bad luck person since I was born
//You are vigorous in words and action.
//The one who snores will always fall asleep first.
//An alien of some sort will be appearing to you shortly!
//Rest is a good thing, but boredom is its brother.
//Do not be overly judgemental of your loved one's intentions or actions.
//Think of how you can assist on a problem, not who to blame.
//The life of every woman or man - the heart of it - is pure and holy joy.
//Take it easy
//Trust your intuition. The universe is guiding your life.
//Use your head, but live in your heart.
//Don't find fault, find a remedy
//It may be those who do most, dream most
//Your passions sweep you away.
//Listen to yourself more often
//Think of mother's exhortations more.
//The gambler is like the fisherman both have beginners luck.
//You are given the chance to take part in an exciting adventure.
//The simplest answer is to act.
//You will always be surrounded by true friends.
//Keep your feet on the ground even though friends flatter you.
//You are the man of righteousness and integrity.
//He who seeks will find.
//The smart thing to do is to begin trusting your intuitions.
//Your many hidden talents will become obvious to those around you.
//Pick a path with heart.
//The human spirit is stronger then anything that can happen to it.
//It takes more than good memory to have good memories.
//Face facts with dignity.
//Be calm when confronting an emergency crisis.
//Do you believe? Endurance and persistence will be rewarded.
//A new wardrobe brings great joy and change in your life.
//Everyone agrees you are the best.
//A new outlook brightens your image and brings new friends.
//Everything will now come your way.
//You will be called to fill a position of high honor and responsibility.
//The eyes believe themselves; the ears believe other people.
//Good beginning is half done.
//Some pursue happiness; you create it.
//It's the worst of times, you need to summon your optimism.
//You are cautious in showing your true self to others.
//Your ability to accomplish tasks will follow with success.
//We all have extraordinary coded within us, waiting to be released.
//You will have a bright future.
//Compassion is a way of being.
//You will always have good luck in your personal affairs.
//The pleasure of what we enjoy is lost by wanting more
//Did you remember to order your take out also?
//Perhaps you've been focusing too much on that one thing..
//Right now there's an energy pushing you in a new direction.
//Everybody feels lucky for having you as a friend.
//When the moment comes, take the top one.
//Sometimes travel to new places leads to great transformation.
//There is always a way - if you are committed.
//Life is too short to waste time hating anyone.
//All the world may not love a lover but they will be watching him.
//Don't just spend time, invest it.
//Life always gets harder near the summit.
//Take the chance while you still have the choice.
//It is much easier to be cirtical than to be correct.
//Enjoy life! It is better to be happy than wise.
//To make the cart go, you must grease the wheels.
//You are contemplating some action which will bring credit upon you.
//Before you wonder Am I doing things right, ask Am I doing the right things?
//You may be disappointed if you fail, but you are doomed if you don't try.
//You will always get what you want through your charm and personality.
//The big issues are work, career, or status right now.
//Your emotional currents are flowing powerfully now.
//Any decision you have to make tomorrow is a good decsion.
//Consume less. Share more. Enjoy life.
//The secret of staying young is good health and lying about your age.
//Spring has sprung. Life is blooming.
//Go ask your mom.
//The possibility of a career change is near.
//The important thing is to never stop questioning.
//Compassion will cure more then condemnation.
//Excuses are easy to manufacture, and hard to sell.
//Put your mind into planning today. Look into the future.
//Listen to life, and you will hear the voice of life crying, Be!
//Broke is only temporaryl poor is a state of mind.
//Here we go. Moo Shu Cereal for breakfast with duck sauce.
//Teamwork: the fuel that allows common people attain uncommon results.
//Hard words break no bones, fine words butter no parsnips.
//We cannot direct the wind but we can adjust the sails.
//You are offered the dream of a lifetime. Say yes!
//Working out the kinks today will make for a better tomorrow.
//You have a curious smile and a mysterious nature.
//Questions provide the key to unlocking our unlimited potential.
//You will enjoy razon-sharp spiritual vision today.
//The wise are aware of their treasure, while fools follow their vanity
//Well-arranged time is the surest sign of a well-arranged mind.
//Never bring unhappy feelings into your home.
//This is really a lovely day. Congratulations!
//Bad luck and ill misfortune will infest your pathetic soul for all eternity.
//A golden egg of opportunity falls into your lap this month.
//You are very grateful for the small pleasures of life.
//today you should be a passenger. Stay close to a driver for a day.
//For hate is never conquered by hate. Hate is conquered by love.
//Service is the rent we pay for the privilege of living on this planet.
//Good clothes open many doors. Go shopping.
//The leader seeks to communicate his vision to his followers.
//Great works are performed not by strength, but by perseverance.
//People who are late are often happier than those who have to wait for them
//Present your best ideas today to an eager and welcoming audience.
//Friends long absent are coming back to you.
//The time is right to make new friends.
//Life to you is a dashing and bold adventure
//You may be hungry soon: order a takeout now.
//Do not hesitate to look for help, an extra hand should always be welcomed.
//How can you have a beautiful ending without making beautiful mistakes?
//Humor is an affirmation of dignity
//He who climbs a ladder must begin at the first step
//What's vice today may be virtue tomorow.
//You have an unusually magnetic personality.
//You will travel to many places.
//Accept yourself
//Be a generous friend and a fair enemy
//Never quit!
//Old friends, old wines and old gold are best
//If your desires are not extravagant, they will be granted
//Every Friend Joys in your Success
//You should be able to undertake and complete anything
//You will enjoy good health, you will be surrounded by luxury
//You are a person of strong sense of duty
//Dream lofty dreams, and as you dream, so shall you become.
//You have a quiet and unobtrusive nature.
//Great thoughts come from the heart.
//You love peace
//Judge not according to the appearance.
//One who admires you greatly is hidden before your eyes.
//Traveling more often is important for your health and happiness.
//You will be sharing great news with all people you love
//You have a reputation for being straightforward and honest.
//You are always welcome in any gathering.
//You will be traveling and coming into a fortune.
//Open up your heart - it can always be closed again.
//Being happy is not always being perfect.
//Next time you have the opportunity, go on a rollercoaster.
//Try everything once, even the things you don't think you will like.
//Life is too short to hold grudges.
//Dream your dream and your dream will dream of you.
//Being alone and being lonely are two different things.
//Don't worry about things in the past, there is nothing you can do about them
now. Don't worry about things that are happening now, make the best of a bad
situation. Don't worry about things in the future, they may never happen.
//Tomorrow, take a moment to do something just for yourself.
//Someone close to you is waiting for you to call.
//A virtual fortune cookie will not satisfy your hunger like that of a home made
one.
//Smile. Tomorrow is another day.
//You can never been certain of success, but you can be certain of failure if
you never try.
//It takes ten times as many muscles to frown as it does to smile.
//Shoot for the moon! If you miss you will still be amongst the stars.
//Keep your eyes open. You never know what you might see.
//Tell them what you really think. Otherwise, nothing will change.
//Let your heart make your decisions - it does not get as confused as your head.
//Working hard will make you live a happy life.
//A pleasant surprise is waiting for you.
QUOTES
);
$i = round(fmod(hexdec(hash('crc32', $seed)), count($quotes)), 0);
return trim(str_replace(array("\r\n", "\n", "\r"), ' ', $quotes[$i]));
}
}

View File

@@ -11,7 +11,6 @@ class OpenClassroomsBridge extends BridgeAbstract {
'u' => array(
'name' => 'Catégorie',
'type' => 'list',
'required' => true,
'values' => array(
'Arts & Culture' => 'arts',
'Code' => 'code',

View File

@@ -0,0 +1,64 @@
<?php
class OsmAndBlogBridge extends BridgeAbstract {
const NAME = 'OsmAnd Blog';
const URI = 'https://osmand.net/';
const DESCRIPTION = 'Get the latest news from OsmAnd.net';
const MAINTAINER = 'fulmeek';
public function collectData() {
$html = getSimpleHTMLDOM(self::URI . 'blog')
or returnServerError('Could not load content');
foreach($html->find('div.article') as $element) {
$item = array();
$objTitle = $element->find('h1', 0);
if (!$objTitle)
$objTitle = $element->find('h2', 0);
if (!$objTitle)
$objTitle = $element->find('h3', 0);
if ($objTitle)
$item['title'] = $objTitle->plaintext;
$objDate = $element->find('meta[pubdate]', 0);
if ($objDate) {
$item['timestamp'] = strtotime($objDate->pubdate);
} else {
$objDate = $element->find('.date', 0);
if ($objDate)
$item['timestamp'] = strtotime($objDate->plaintext);
}
$this->cleanupContent($element, $objTitle, $objDate, $element->find('.date', 0));
$item['content'] = $element->innertext;
$objLink = $html->find('.articlelinklist a', 0);
if ($objLink) {
$item['uri'] = $this->filterURL($objLink->href);
} else {
$item['uri'] = 'urn:sha1:' . hash('sha1', $item['content']);
}
$this->items[] = $item;
}
}
private function filterURL($url) {
if (strpos($url, '://') === false)
return self::URI . ltrim($url, '/');
return $url;
}
private function cleanupContent($content, ...$removeItems) {
foreach ($removeItems as $obj) {
if ($obj) $obj->outertext = '';
}
foreach ($content->find('img') as $obj) {
$obj->src = $this->filterURL($obj->src);
}
foreach ($content->find('a') as $obj) {
$obj->href = $this->filterURL($obj->href);
$obj->target = '_blank';
}
}
}

View File

@@ -15,7 +15,6 @@ class PixivBridge extends BridgeAbstract {
),
));
public function collectData(){
$html = getContents(static::URI . 'search.php?word=' . urlencode($this->getInput('tag')))
@@ -70,5 +69,4 @@ class PixivBridge extends BridgeAbstract {
return 'cache/pixiv_img/' . $illustId . '.jpeg';
}
}

View File

@@ -0,0 +1,106 @@
<?php
class RoadAndTrackBridge extends BridgeAbstract {
const MAINTAINER = 'teromene';
const NAME = 'Road And Track Bridge';
const URI = 'https://www.roadandtrack.com/';
const CACHE_TIMEOUT = 86400; // 24h
const DESCRIPTION = 'Returns the latest news from Road & Track.';
const PARAMETERS = array(
array(
'new-cars' => array(
'name' => 'New Cars',
'type' => 'checkbox',
'exampleValue' => 'checked',
'title' => 'Activate to load New Cars articles'
),
'motorsports' => array(
'name' => 'Motorsports',
'type' => 'checkbox',
'exampleValue' => 'checked',
'title' => 'Activate to load Motorsports articles'
),
'car-culture' => array(
'name' => 'Car Culture',
'type' => 'checkbox',
'exampleValue' => 'checked',
'title' => 'Activate to load Car Culture articles'
),
'car-shows' => array(
'name' => 'Car shows',
'type' => 'checkbox',
'exampleValue' => 'checked',
'title' => 'Activate to load Car shows articles'
)
)
);
const SIG_URL = 'https://cloud.mazdigital.com/feeds/production/comboapp/204/api/v3/';
public function collectData() {
//Magic
$signVal = '?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9jbG91ZC5tYXpkaWd';
$signVal .= 'pdGFsLmNvbS9mZWVkcy9wcm9kdWN0aW9uL2NvbWJvYXBwLzIwNC8qIiwiQ29uZGl0aW9uIj';
$signVal .= 'p7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNTUyNTU5MDUzfSwiSXBBZGRyZ';
$signVal .= 'XNzIjp7IkFXUzpTb3VyY2VJcCI6IjAuMC4wLjAvMCJ9fX1dfQ__&Signature=jgS~Jccjs';
$signVal .= 'lXMMywWesmwDpUbHvEmrADRP7iBRzT~OiP-O~zI-8TtQzqTP7GUrpB9~v69CvhO7-JVtw94';
$signVal .= 'VC3N6lQrwsxTTIhpS57YGeV~MbZx~P653yUV7jb3jpJE2yUawfXnEkD-XzOIn8-caMo~14i';
$signVal .= 'KuWV9KNDkTJaRgOMy0rrVpWqiuBjCu5s5B8Ylt2qwcpOvHjXSqG9IY5c7GUIXKsk8yXzGFi';
$signVal .= 'yzy8hfuGgdx0n7fgl7c4-EoDgQaz~U76g0epejPxV5Csj16rCCfAqBU5kZJnACZ1vvOvRcV';
$signVal .= 'Wiu8KUuUuCS04SPmJ73Y5XoY8~uXRScxZG1kAFTIAhT4nYVlg__&Key-Pair-Id=APKAIZB';
$signVal .= 'QNNSW4WGIFP4Q';
$newsElements = array();
if($this->getInput('new-cars')) {
$newsElements = array_merge($newsElements,
json_decode(getContents(self::SIG_URL . '7591/item_feed' . $signVal))
);
}
if($this->getInput('motorsports')) {
$newsElements = array_merge($newsElements,
json_decode(getContents(self::SIG_URL . '7590/item_feed' . $signVal))
);
}
if($this->getInput('car-culture')) {
$newsElements = array_merge($newsElements,
json_decode(getContents(self::SIG_URL . '7588/item_feed' . $signVal))
);
}
if($this->getInput('car-shows')) {
$newsElements = array_merge($newsElements,
json_decode(getContents(self::SIG_URL . '7589/item_feed' . $signVal))
);
}
usort($newsElements, function($a, $b) {
return $b->published - $a->published;
});
$limit = 19;
foreach($newsElements as $element) {
$item = array();
$item['uri'] = $element->sourceUrl;
$item['timestamp'] = $element->published;
$item['enclosures'] = array($element->cover->url);
$item['title'] = $element->title;
$item['content'] = $this->getArticleContent($element);
$this->items[] = $item;
if($limit > 0) {
$limit--;
} else {
break;
}
}
}
private function getArticleContent($article) {
return getContents($article->contentUrl);
}
}

View File

@@ -6,7 +6,6 @@ class Rue89Bridge extends BridgeAbstract {
const URI = 'https://www.nouvelobs.com/rue89/';
const DESCRIPTION = 'Returns the newest posts from Rue89';
public function collectData() {
$jsonArticles = getContents('https://appdata.nouvelobs.com/rue89/feed.json')
@@ -46,5 +45,4 @@ class Rue89Bridge extends BridgeAbstract {
return $item;
}
}

View File

@@ -35,5 +35,4 @@ class Shimmie2Bridge extends DanbooruBridge {
return $item;
}
}

View File

@@ -18,7 +18,6 @@ class SkimfeedBridge extends BridgeAbstract {
'box_channel' => array(
'name' => 'Channel',
'type' => 'list',
'required' => true,
'title' => 'Select your channel',
'values' => array(
'Hacker News' => '/news/hacker-news.html',
@@ -68,7 +67,6 @@ class SkimfeedBridge extends BridgeAbstract {
'tech_channel' => array(
'name' => 'Tech channel',
'type' => 'list',
'required' => true,
'title' => 'Select your tech channel',
'values' => array(
'Agg' => array(
@@ -804,7 +802,6 @@ EOD;
EOD;
}
/**
* Checks if the reported skimfeed version is compatible
*/
@@ -821,5 +818,4 @@ EOD;
return false;
}
}

View File

@@ -14,7 +14,7 @@ class SoundCloudBridge extends BridgeAbstract {
)
));
const CLIENT_ID = '4jkoEFmZEDaqjwJ9Eih4ATNhcH3vMVfp';
const CLIENT_ID = 'W0KEWWILAjDiRH89X0jpwzuq6rbSK08R';
public function collectData(){
@@ -32,7 +32,8 @@ class SoundCloudBridge extends BridgeAbstract {
. self::CLIENT_ID
)) or returnServerError('No results for this user');
for($i = 0; $i < 10; $i++) {
$numTracks = min(count($tracks), 10);
for($i = 0; $i < $numTracks; $i++) {
$item = array();
$item['author'] = $tracks[$i]->user->username;
$item['title'] = $tracks[$i]->user->username . ' - ' . $tracks[$i]->title;
@@ -54,6 +55,7 @@ class SoundCloudBridge extends BridgeAbstract {
}
}
public function getName(){
if(!is_null($this->getInput('u'))) {
return self::NAME . ' - ' . $this->getInput('u');

View File

@@ -57,5 +57,4 @@ class SupInfoBridge extends BridgeAbstract {
return $item;
}
}

View File

@@ -16,13 +16,13 @@ class TheHackerNewsBridge extends BridgeAbstract {
if($limit < 5) {
$article_url = $element->find('a.story-link', 0)->href;
$article_author = trim($element->find('i.fa-user', 0)->parent()->plaintext);
$article_author = trim($element->find('i.icon-user', 0)->parent()->plaintext);
$article_title = $element->find('h2.home-title', 0)->plaintext;
//Date without time
$article_timestamp = strtotime(
extractFromDelimiters(
$element->find('i.fa-calendar', 0)->parent()->outertext,
$element->find('i.icon-calendar', 0)->parent()->outertext,
'</i>',
'<span>'
)

View File

@@ -112,8 +112,7 @@ class ThingiverseBridge extends BridgeAbstract {
'3D Printer Parts' => '128',
'3D Printers' => '126',
'3D Printing Tests' => '129',
),
'defaultValue' => ''
)
),
'showimage' => array(
'name' => 'Show image in content',
@@ -163,5 +162,4 @@ class ThingiverseBridge extends BridgeAbstract {
return parent::getURI();
}
}

687
bridges/TrelloBridge.php Normal file
View File

@@ -0,0 +1,687 @@
<?php
class TrelloBridge extends BridgeAbstract {
const NAME = 'Trello Bridge';
const URI = 'https://trello.com/';
const CACHE_TIMEOUT = 300; // 5min
const DESCRIPTION = 'Returns activity on Trello boards or cards';
const MAINTAINER = 'Roliga';
const PARAMETERS = array(
'Board' => array(
'b' => array(
'name' => 'Board ID',
'required' => true,
'exampleValue' => 'g9mdhdzg',
'title' => 'Taken from Trello URL, e.g. trello.com/b/[Board ID]'
)
),
'Card' => array(
'c' => array(
'name' => 'Card ID',
'required' => true,
'exampleValue' => '8vddc9pE',
'title' => 'Taken from Trello URL, e.g. trello.com/c/[Card ID]'
)
)
);
/*
* This was extracted from webpack on a Trello page, e.g. trello.com/b/g9mdhdzg
* In the browser's inspector/debugger go to the Debugger (Firefox) or
* Sources (Chromium) tab, these values can be found at:
* webpack:///resources/strings/actions/en.json
*/
const ACTION_TEXTS = array(
'action_accept_enterprise_join_request'
=> '{memberCreator} added team {organization} to the enterprise {enterprise}',
'action_add_attachment_to_card'
=> '{memberCreator} attached {attachment} to {card} {attachmentPreview}',
'action_add_attachment_to_card@card'
=> '{memberCreator} attached {attachment} to this card {attachmentPreview}',
'action_add_checklist_to_card'
=> '{memberCreator} added {checklist} to {card}',
'action_add_checklist_to_card@card'
=> '{memberCreator} added {checklist} to this card',
'action_add_label_to_card'
=> '{memberCreator} added the {label} label to {card}',
'action_add_label_to_card@card'
=> '{memberCreator} added the {label} label to this card',
'action_add_organization_to_enterprise'
=> '{memberCreator} added team {organization} to the enterprise {enterprise}',
'action_add_to_organization_board'
=> '{memberCreator} added {board} to {organization}',
'action_add_to_organization_board@board'
=> '{memberCreator} added this board to {organization}',
'action_added_a_due_date'
=> '{memberCreator} set {card} to be due {date}',
'action_added_a_due_date@card'
=> '{memberCreator} set this card to be due {date}',
'action_added_list_to_board'
=> '{memberCreator} added list {list} to {board}',
'action_added_list_to_board@board'
=> '{memberCreator} added {list} to this board',
'action_added_member_to_board'
=> '{memberCreator} added {member} to {board}',
'action_added_member_to_board@board'
=> '{memberCreator} added {member} to this board',
'action_added_member_to_board_as_admin'
=> '{memberCreator} added {member} to {board} as an admin',
'action_added_member_to_board_as_admin@board'
=> '{memberCreator} added {member} to this board as an admin',
'action_added_member_to_board_as_observer'
=> '{memberCreator} added {member} to {board} as an observer',
'action_added_member_to_board_as_observer@board'
=> '{memberCreator} added {member} to this board as an observer',
'action_added_member_to_card'
=> '{memberCreator} added {member} to {card}',
'action_added_member_to_card@card'
=> '{memberCreator} added {member} to this card',
'action_added_member_to_organization'
=> '{memberCreator} added {member} to {organization}',
'action_added_member_to_organization_as_admin'
=> '{memberCreator} added {member} to {organization} as an admin',
'action_admins_visibility'
=> 'its admins',
'action_another_board'
=> 'another board',
'action_archived_card'
=> '{memberCreator} archived {card}',
'action_archived_card@card'
=> '{memberCreator} archived this card',
'action_archived_list'
=> '{memberCreator} archived list {list}',
'action_became_a_normal_user_in_organization'
=> '{memberCreator} became a normal user in {organization}',
'action_became_a_normal_user_on'
=> '{memberCreator} became a normal user on {board}',
'action_became_a_normal_user_on@board'
=> '{memberCreator} became a normal user on this board',
'action_became_an_admin_of_organization'
=> '{memberCreator} became an admin of {organization}',
'action_board_perm_level'
=> '{memberCreator} made {board} visible to {level}',
'action_board_perm_level@board'
=> '{memberCreator} made this board visible to {level}',
'action_calendar'
=> 'calendar',
'action_cardAging'
=> 'card aging',
'action_changed_a_due_date'
=> '{memberCreator} changed the due date of {card} to {date}',
'action_changed_a_due_date@card'
=> '{memberCreator} changed the due date of this card to {date}',
'action_changed_board_background'
=> '{memberCreator} changed the background of {board}',
'action_changed_board_background@board'
=> '{memberCreator} changed the background of this board',
'action_changed_description_of_card'
=> '{memberCreator} changed description of {card}',
'action_changed_description_of_card@card'
=> '{memberCreator} changed description of this card',
'action_changed_description_of_organization'
=> '{memberCreator} changed description of {organization}',
'action_changed_display_name_of_organization'
=> '{memberCreator} changed display name of {organization}',
'action_changed_name_of_organization'
=> '{memberCreator} changed name of {organization}',
'action_changed_website_of_organization'
=> '{memberCreator} changed website of {organization}',
'action_closed_board'
=> '{memberCreator} closed {board}',
'action_closed_board@board'
=> '{memberCreator} closed this board',
'action_comment_on_card'
=> '{memberCreator} {contextOn} {card} {comment}',
'action_comment_on_card@card'
=> '{memberCreator} {comment}',
'action_completed_checkitem'
=> '{memberCreator} completed {checkitem} on {card}',
'action_completed_checkitem@card'
=> '{memberCreator} completed {checkitem} on this card',
'action_convert_to_card_from_checkitem'
=> '{memberCreator} converted {card} from a checklist item on {cardSource}',
'action_convert_to_card_from_checkitem@card'
=> '{memberCreator} converted this card from a checklist item on {cardSource}',
'action_convert_to_card_from_checkitem@cardSource'
=> '{memberCreator} converted {card} from a checklist item on this card',
'action_copy_board'
=> '{memberCreator} copied this board from {board}',
'action_copy_card'
=> '{memberCreator} copied {card} from {cardSource} in list {list}',
'action_copy_card@card'
=> '{memberCreator} copied this card from {cardSource} in list {list}',
'action_copy_comment_from_card'
=> '{memberCreator} copied comment by {member} from card {card} {comment}',
'action_create_board'
=> '{memberCreator} created {board}',
'action_create_board@board'
=> '{memberCreator} created this board',
'action_create_card'
=> '{memberCreator} added {card} to {list}',
'action_create_card@card'
=> '{memberCreator} added this card to {list}',
'action_create_custom_field'
=> '{memberCreator} created the {customField} custom field on {board}',
'action_create_custom_field@board'
=> '{memberCreator} created the {customField} custom field on this board',
'action_create_enterprise_join_request'
=> '{memberCreator} requested to add team {organization} to the enterprise {enterprise}',
'action_created_an_invitation_to_board'
=> '{memberCreator} created an invitation to {board}',
'action_created_an_invitation_to_board@board'
=> '{memberCreator} created an invitation to this board',
'action_created_an_invitation_to_organization'
=> '{memberCreator} created an invitation to {organization}',
'action_created_checklist_on_board'
=> '{memberCreator} created {checklist} on {board}',
'action_created_checklist_on_board@board'
=> '{memberCreator} created {checklist} on this board',
'action_created_organization'
=> '{memberCreator} created {organization}',
'action_decline_enterprise_join_request'
=> '{memberCreator} declined the request to add team {organization} to the enterprise {enterprise}',
'action_delete_attachment_from_card'
=> '{memberCreator} deleted the {attachment} attachment from {card}',
'action_delete_attachment_from_card@card'
=> '{memberCreator} deleted the {attachment} attachment from this card',
'action_delete_card'
=> '{memberCreator} deleted card #{idCard} from {list}',
'action_delete_custom_field'
=> '{memberCreator} deleted the {customField} custom field from {board}',
'action_delete_custom_field@board'
=> '{memberCreator} deleted the {customField} custom field from this board',
'action_deleted_account'
=> '[deleted account]',
'action_deleted_an_invitation_to_board'
=> '{memberCreator} deleted an invitation to {board}',
'action_deleted_an_invitation_to_board@board'
=> '{memberCreator} deleted an invitation to this board',
'action_deleted_an_invitation_to_organization'
=> '{memberCreator} deleted an invitation to {organization}',
'action_deleted_checkitem'
=> '{memberCreator} deleted task {checkitem} on {checklist}',
'action_disabled_calendar_feed'
=> '{memberCreator} disabled the iCalendar feed on {board}',
'action_disabled_calendar_feed@board'
=> '{memberCreator} disabled the iCalendar feed on this board',
'action_disabled_card_covers'
=> '{memberCreator} disabled card cover images on {board}',
'action_disabled_card_covers@board'
=> '{memberCreator} disabled card cover images on this board',
'action_disabled_commenting'
=> '{memberCreator} disabled commenting on {board}',
'action_disabled_commenting@board'
=> '{memberCreator} disabled commenting on this board',
'action_disabled_inviting'
=> '{memberCreator} disabled inviting on {board}',
'action_disabled_inviting@board'
=> '{memberCreator} disabled inviting on this board',
'action_disabled_plugin'
=> '{memberCreator} disabled the {plugin} Power-Up',
'action_disabled_powerup'
=> '{memberCreator} disabled the {powerup} Power-Up',
'action_disabled_self_join'
=> '{memberCreator} disabled self join on {board}',
'action_disabled_self_join@board'
=> '{memberCreator} disabled self join on this board',
'action_disabled_voting'
=> '{memberCreator} disabled voting on {board}',
'action_disabled_voting@board'
=> '{memberCreator} disabled voting on this board',
'action_due_date_change'
=> '{memberCreator}',
'action_email_card'
=> '{memberCreator} emailed {card} to {list}',
'action_email_card@card'
=> '{memberCreator} emailed this card to {list}',
'action_email_card_from'
=> '{memberCreator} emailed {card} to {list} from {from}',
'action_email_card_from@card'
=> '{memberCreator} emailed this card to {list} from {from}',
'action_enabled_calendar_feed'
=> '{memberCreator} enabled the iCalendar feed on {board}',
'action_enabled_calendar_feed@board'
=> '{memberCreator} enabled the iCalendar feed on this board',
'action_enabled_card_covers'
=> '{memberCreator} enabled card cover images on {board}',
'action_enabled_card_covers@board'
=> '{memberCreator} enabled card cover images on this board',
'action_enabled_plugin'
=> '{memberCreator} enabled the {plugin} Power-Up',
'action_enabled_powerup'
=> '{memberCreator} enabled the {powerup} Power-Up',
'action_enabled_self_join'
=> '{memberCreator} enabled self join on {board}',
'action_enabled_self_join@board'
=> '{memberCreator} enabled self join on this board',
'action_hid_board'
=> '{memberCreator} hid {board}',
'action_hid_board@board'
=> '{memberCreator} hid this board',
'action_invited_an_unconfirmed_member_to_board'
=> '{memberCreator} invited an unconfirmed member to {board}',
'action_invited_an_unconfirmed_member_to_board@board'
=> '{memberCreator} invited an unconfirmed member to this board',
'action_invited_an_unconfirmed_member_to_organization'
=> '{memberCreator} invited an unconfirmed member to {organization}',
'action_joined_board'
=> '{memberCreator} joined {board}',
'action_joined_board@board'
=> '{memberCreator} joined this board',
'action_joined_board_by_invitation_link'
=> '{memberCreator} joined {board} with an invitation link from {memberInviter}',
'action_joined_board_by_invitation_link@board'
=> '{memberCreator} joined this board with an invitation link from {memberInviter}',
'action_joined_organization'
=> '{memberCreator} joined {organization}',
'action_joined_organization_by_invitation_link'
=> '{memberCreator} joined {organization} with an invitation link from {memberInviter}',
'action_left_board'
=> '{memberCreator} left {board}',
'action_left_board@board'
=> '{memberCreator} left this board',
'action_left_organization'
=> '{memberCreator} left {organization}',
'action_made_a_normal_user_in_organization'
=> '{memberCreator} made {member} a normal user in {organization}',
'action_made_a_normal_user_on'
=> '{memberCreator} made {member} a normal user on {board}',
'action_made_a_normal_user_on@board'
=> '{memberCreator} made {member} a normal user on this board',
'action_made_admin_of_board'
=> '{memberCreator} made {member} an admin of {board}',
'action_made_admin_of_board@board'
=> '{memberCreator} made {member} an admin of this board',
'action_made_an_admin_of_organization'
=> '{memberCreator} made {member} an admin of {organization}',
'action_made_commenting_on'
=> '{memberCreator} made commenting on {board} available to {level}',
'action_made_commenting_on@board'
=> '{memberCreator} made commenting on this board available to {level}',
'action_made_inviting_on'
=> '{memberCreator} made inviting on {board} available to {level}',
'action_made_inviting_on@board'
=> '{memberCreator} made inviting on this board available to {level}',
'action_made_observer_of_board'
=> '{memberCreator} made {member} an observer of {board}',
'action_made_observer_of_board@board'
=> '{memberCreator} made {member} an observer of this board',
'action_made_self_admin_of_board'
=> '{memberCreator} made themselves an admin of {board}',
'action_made_self_admin_of_board@board'
=> '{memberCreator} made themselves an admin of this board',
'action_made_self_observer_of_board'
=> '{memberCreator} became an observer of {board}',
'action_made_self_observer_of_board@board'
=> '{memberCreator} became an observer of this board',
'action_made_voting_on'
=> '{memberCreator} made voting on {board} available to {level}',
'action_made_voting_on@board'
=> '{memberCreator} made voting on this board available to {level}',
'action_marked_checkitem_incomplete'
=> '{memberCreator} marked {checkitem} incomplete on {card}',
'action_marked_checkitem_incomplete@card'
=> '{memberCreator} marked {checkitem} incomplete on this card',
'action_marked_the_due_date_complete'
=> '{memberCreator} marked the due date on {card} complete',
'action_marked_the_due_date_complete@card'
=> '{memberCreator} marked the due date complete',
'action_marked_the_due_date_incomplete'
=> '{memberCreator} marked the due date on {card} incomplete',
'action_marked_the_due_date_incomplete@card'
=> '{memberCreator} marked the due date incomplete',
'action_member_joined_card'
=> '{memberCreator} joined {card}',
'action_member_joined_card@card'
=> '{memberCreator} joined this card',
'action_member_left_card'
=> '{memberCreator} left {card}',
'action_member_left_card@card'
=> '{memberCreator} left this card',
'action_members_visibility'
=> 'its members',
'action_move_card_from_board'
=> '{memberCreator} transferred {card} to {board}',
'action_move_card_from_board@card'
=> '{memberCreator} transferred this card to {board}',
'action_move_card_from_list_to_list'
=> '{memberCreator} moved {card} from {listBefore} to {listAfter}',
'action_move_card_from_list_to_list@card'
=> '{memberCreator} moved this card from {listBefore} to {listAfter}',
'action_move_card_to_board'
=> '{memberCreator} transferred {card} from {board}',
'action_move_card_to_board@card'
=> '{memberCreator} transferred this card from {board}',
'action_move_list_from_board'
=> '{memberCreator} transferred {list} to {board}',
'action_move_list_to_board'
=> '{memberCreator} transferred {list} from {board}',
'action_moved_card_higher'
=> '{memberCreator} moved {card} higher',
'action_moved_card_higher@card'
=> '{memberCreator} moved this card higher',
'action_moved_card_lower'
=> '{memberCreator} moved {card} lower',
'action_moved_card_lower@card'
=> '{memberCreator} moved this card lower',
'action_moved_checkitem_higher'
=> '{memberCreator} moved {checkitem} higher in the checklist {checklist}',
'action_moved_checkitem_lower'
=> '{memberCreator} moved {checkitem} higher in the checklist {checklist}',
'action_moved_list_left'
=> '{memberCreator} moved list {list} left on {board}',
'action_moved_list_left@board'
=> '{memberCreator} moved {list} left on this board',
'action_moved_list_right'
=> '{memberCreator} moved list {list} right on {board}',
'action_moved_list_right@board'
=> '{memberCreator} moved {list} right on this board',
'action_observers_visibility'
=> 'members and observers',
'action_on'
=> 'on',
'action_org_visibility'
=> 'members of its team',
'action_public_visibility'
=> 'the public',
'action_remove_checklist_from_card'
=> '{memberCreator} removed {checklist} from {card}',
'action_remove_checklist_from_card@card'
=> '{memberCreator} removed {checklist} from this card',
'action_remove_from_organization_board'
=> '{memberCreator} removed {board} from {organization}',
'action_remove_from_organization_board@board'
=> '{memberCreator} removed this board from {organization}',
'action_remove_label_from_card'
=> '{memberCreator} removed the {label} label from {card}',
'action_remove_label_from_card@card'
=> '{memberCreator} removed the {label} label from this card',
'action_remove_organization_from_enterprise'
=> '{memberCreator} removed team {organization} from the enterprise {enterprise}',
'action_removed_a_due_date'
=> '{memberCreator} removed the due date from {card}',
'action_removed_a_due_date@card'
=> '{memberCreator} removed the due date from this card',
'action_removed_from_board'
=> '{memberCreator} removed {member} from {board}',
'action_removed_from_board@board'
=> '{memberCreator} removed {member} from this board',
'action_removed_member_from_card'
=> '{memberCreator} removed {member} from {card}',
'action_removed_member_from_card@card'
=> '{memberCreator} removed {member} from this card',
'action_removed_member_from_organization'
=> '{memberCreator} removed {member} from {organization}',
'action_removed_vote_for_card'
=> '{memberCreator} removed vote for {card}',
'action_removed_vote_for_card@card'
=> '{memberCreator} removed vote for this card',
'action_rename_custom_field'
=> '{memberCreator} renamed the {customField} custom field on {board} (from {name})',
'action_rename_custom_field@board'
=> '{memberCreator} renamed the {customField} custom field on this board (from {name})',
'action_renamed_card'
=> '{memberCreator} renamed {card} (from {name})',
'action_renamed_card@card'
=> '{memberCreator} renamed this card (from {name})',
'action_renamed_checkitem'
=> '{memberCreator} renamed {checkitem} (from {name})',
'action_renamed_checklist'
=> '{memberCreator} renamed {checklist} (from {name})',
'action_renamed_list'
=> '{memberCreator} renamed list {list} (from {name})',
'action_reopened_board'
=> '{memberCreator} re-opened {board}',
'action_reopened_board@board'
=> '{memberCreator} re-opened this board',
'action_sent_card_to_board'
=> '{memberCreator} sent {card} to the board',
'action_sent_card_to_board@card'
=> '{memberCreator} sent this card to the board',
'action_sent_list_to_board'
=> '{memberCreator} sent list {list} to the board',
'action_set_card_aging_mode_pirate'
=> '{memberCreator} changed card aging to pirate mode',
'action_set_card_aging_mode_regular'
=> '{memberCreator} changed card aging to regular mode',
'action_update_board_desc'
=> '{memberCreator} changed description of {board}',
'action_update_board_desc@board'
=> '{memberCreator} changed description of this board',
'action_update_board_name'
=> '{memberCreator} renamed {board} (from {name})',
'action_update_board_name@board'
=> '{memberCreator} renamed this board (from {name})',
'action_update_custom_field'
=> '{memberCreator} updated the {customField} custom field on {board}',
'action_update_custom_field@board'
=> '{memberCreator} updated the {customField} custom field on this board',
'action_update_custom_field_item'
=> '{memberCreator} updated the value for the {customFieldItem} custom field on {card}',
'action_update_custom_field_item@card'
=> '{memberCreator} updated the value for the {customFieldItem} custom field on this card',
'action_updated_their_bio'
=> '{memberCreator} updated their bio',
'action_updated_their_display_name'
=> '{memberCreator} updated their display name',
'action_updated_their_initials'
=> '{memberCreator} updated their initials',
'action_updated_their_username'
=> '{memberCreator} updated their username',
'action_vote_on_card'
=> '{memberCreator} voted for {card}',
'action_vote_on_card@card'
=> '{memberCreator} voted for this card',
'action_voting'
=> 'voting',
'action_withdraw_enterprise_join_request'
=> '{memberCreator} withdrew a request to add team {organization} to the enterprise {enterprise}'
);
const REQUEST_ACTIONS_BOARDS = array(
'addAttachmentToCard',
'addChecklistToCard',
'addMemberToCard',
'commentCard',
'copyCommentCard',
'convertToCardFromCheckItem',
'createCard',
'copyCard',
'deleteAttachmentFromCard',
'emailCard',
'moveCardFromBoard',
'moveCardToBoard',
'removeChecklistFromCard',
'removeMemberFromCard',
'updateCard:idList',
'updateCard:closed',
'updateCard:due',
'updateCard:dueComplete',
'updateCheckItemStateOnCard',
'updateCustomFieldItem',
'addMemberToBoard',
'addToOrganizationBoard',
'copyBoard',
'createBoard',
'createCustomField',
'createList',
'deleteCard',
'deleteCustomField',
'disablePlugin',
'disablePowerUp',
'enablePlugin',
'enablePowerUp',
'makeAdminOfBoard',
'makeNormalMemberOfBoard',
'makeObserverOfBoard',
'moveListFromBoard',
'moveListToBoard',
'removeFromOrganizationBoard',
'unconfirmedBoardInvitation',
'unconfirmedOrganizationInvitation',
'updateBoard',
'updateCustomField',
'updateList:closed'
);
const REQUEST_ACTIONS_CARDS = array(
'addAttachmentToCard',
'addChecklistToCard',
'addMemberToCard',
'commentCard',
'copyCommentCard',
'convertToCardFromCheckItem',
'createCard',
'copyCard',
'deleteAttachmentFromCard',
'emailCard',
'moveCardFromBoard',
'moveCardToBoard',
'removeChecklistFromCard',
'removeMemberFromCard',
'updateCard:idList',
'updateCard:closed',
'updateCard:due',
'updateCard:dueComplete',
'updateCheckItemStateOnCard',
'updateCustomFieldItem'
);
private $feedName = '';
private $feedURI = '';
private function queryAPI($path, $params = array()) {
$data = json_decode(getContents('https://trello.com/1/'
. $path
. '?'
. http_build_query($params)))
or returnServerError('Failed to query trello API');
return $data;
}
private function renderAction($action, $textOnly = false) {
if(!array_key_exists($action->display->translationKey, self::ACTION_TEXTS)) {
return '';
}
$strings = array();
$entities = (array)$action->display->entities;
foreach($entities as $entity_name => $entity) {
$type = $entity->type;
if($type === 'attachmentPreview'
&& !$textOnly
&& isset($entity->originalUrl)) {
$string = '<p><a href="'
. $entity->originalUrl
. '"><img src="'
. $entity->previewUrl
. '"></a></p>';
} elseif($type === 'card' && !$textOnly) {
$string = '<a href="https://trello.com/c/'
. $entity->shortLink
. '">'
. $entity->text
. '</a>';
} elseif($type === 'member' && !$textOnly) {
$string = '<a href="https://trello.com/'
. $entity->username
. '">'
. $entity->text
. '</a>';
} elseif($type === 'date') {
$string = gmdate('M j, Y \a\t g:i A T', strtotime($entity->date));
} elseif($type === 'translatable') {
$string = self::ACTION_TEXTS[$entity->translationKey];
} else {
if(isset($entity->text)) {
$string = $entity->text;
} else {
$string = '';
}
}
$strings['{' . $entity_name . '}'] = $string;
}
return str_replace(array_keys($strings),
array_values($strings),
self::ACTION_TEXTS[$action->display->translationKey]);
}
public function collectData() {
$apiParams = array(
'actions_display' => 'true',
'fields' => 'name,url'
);
switch($this->queriedContext) {
case 'Board':
$apiParams['actions'] = implode(',', self::REQUEST_ACTIONS_BOARDS);
$data = $this->queryAPI('boards/' . $this->getInput('b'), $apiParams);
break;
case 'Card':
$apiParams['actions'] = implode(',', self::REQUEST_ACTIONS_CARDS);
$data = $this->queryAPI('cards/' . $this->getInput('c'), $apiParams);
break;
default:
returnClientError('Invalid context');
}
$this->feedName = $data->name;
$this->feedURI = $data->url;
foreach($data->actions as $action) {
$item = array();
$item['title'] = $this->renderAction($action, true);
$item['timestamp'] = strtotime($action->date);
$item['author'] = $action->memberCreator->fullName;
$item['categories'] = array(
'trello',
$action->data->board->name,
$action->type
);
if(isset($action->data->card)) {
$item['categories'][] = $action->data->card->name;
$item['uri'] = 'https://trello.com/c/'
. $action->data->card->shortLink
. '#action-'
. $action->id;
} else {
$item['uri'] = 'https://trello.com/b/'
. $action->data->board->shortLink;
}
$item['content'] = $this->renderAction($action, false);
if(isset($action->data->attachment->url)) {
$item['enclosures'] = array($action->data->attachment->url);
}
$this->items[] = $item;
}
}
public function detectParameters($url) {
$regex = '/^(https?:\/\/)?trello\.com\/([bc])\/([^\/?\n]+)/';
if(preg_match($regex, $url, $matches) > 0) {
return array($matches[2] => $matches[3]);
} else {
return null;
}
}
public function getURI() {
switch($this->queriedContext) {
case 'Board':
case 'Card':
return $this->feedURI;
default: return parent::getURI();
}
}
public function getName() {
switch($this->queriedContext) {
case 'Board':
case 'Card':
return $this->feedName;
default: return parent::getName();
}
}
}

View File

@@ -16,6 +16,11 @@ class TwitterBridge extends BridgeAbstract {
'name' => 'Hide images in tweets',
'type' => 'checkbox',
'title' => 'Activate to hide images in tweets'
),
'noimgscaling' => array(
'name' => 'Disable image scaling',
'type' => 'checkbox',
'title' => 'Activate to disable image scaling in tweets (keeps original image)'
)
),
'By keyword or hashtag' => array(
@@ -160,7 +165,7 @@ class TwitterBridge extends BridgeAbstract {
// Skip retweets?
if($this->getInput('noretweet')
&& $tweet->getAttribute('data-screen-name') !== $this->getInput('u')) {
&& strcasecmp($tweet->getAttribute('data-screen-name'), $this->getInput('u'))) {
continue;
}
@@ -184,6 +189,9 @@ class TwitterBridge extends BridgeAbstract {
$item['fullname'] = htmlspecialchars_decode($tweet->getAttribute('data-name'), ENT_QUOTES);
// get author
$item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')';
if(strcasecmp($tweet->getAttribute('data-screen-name'), $this->getInput('u'))) {
$item['author'] .= ' RT: @' . $this->getInput('u');
}
// get avatar link
$item['avatar'] = $tweet->find('img', 0)->src;
// get TweetID
@@ -239,14 +247,18 @@ EOD;
$image_html = '';
$image = $this->getImageURI($tweet);
if(!$this->getInput('noimg') && !is_null($image)) {
// Set image scaling
$image_orig = $this->getInput('noimgscaling') ? $image : $image . ':orig';
$image_thumb = $this->getInput('noimgscaling') ? $image : $image . ':thumb';
// add enclosures
$item['enclosures'] = array($image . ':orig');
$item['enclosures'] = array($image_orig);
$image_html = <<<EOD
<a href="{$image}:orig">
<a href="{$image_orig}">
<img
style="align:top; max-width:558px; border:1px solid black;"
src="{$image}:thumb" />
src="{$image_thumb}" />
</a>
EOD;
}
@@ -281,14 +293,18 @@ EOD;
$quotedImage_html = '';
$quotedImage = $this->getQuotedImageURI($tweet);
if(!$this->getInput('noimg') && !is_null($quotedImage)) {
// Set image scaling
$quotedImage_orig = $this->getInput('noimgscaling') ? $quotedImage : $quotedImage . ':orig';
$quotedImage_thumb = $this->getInput('noimgscaling') ? $quotedImage : $quotedImage . ':thumb';
// add enclosures
$item['enclosures'] = array($quotedImage . ':orig');
$item['enclosures'] = array($quotedImage_orig);
$quotedImage_html = <<<EOD
<a href="{$quotedImage}:orig">
<a href="{$quotedImage_orig}">
<img
style="align:top; max-width:558px; border:1px solid black;"
src="{$quotedImage}:thumb" />
src="{$quotedImage_thumb}" />
</a>
EOD;
}

View File

@@ -106,5 +106,4 @@ class UsbekEtRicaBridge extends BridgeAbstract {
private function replaceUriInHtmlElement($element){
return str_replace('href="/', 'href="' . $this->getURI() . '/', $element->innertext);
}
}

View File

@@ -0,0 +1,31 @@
<?php
class VMwareSecurityBridge extends BridgeAbstract {
const MAINTAINER = 'm0le.net';
const NAME = 'VMware Security Advisories';
const URI = 'https://www.vmware.com/security/advisories.html';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'VMware Security Advisories';
const WEBROOT = 'https://www.vmware.com';
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request VSA.');
$html = defaultLinkTo($html, self::WEBROOT);
$item = array();
$articles = $html->find('div[class="news_block"]');
foreach ($articles as $element) {
$item['uri'] = $element->find('a', 0)->getAttribute('href');
$title = $element->find('a', 0)->innertext;
$item['title'] = $title;
$item['timestamp'] = strtotime($element->find('p', 0)->innertext);
$item['content'] = $element->find('p', 1)->innertext;
$item['uid'] = $title;
$this->items[] = $item;
}
}
}

View File

@@ -55,6 +55,7 @@ class WhydBridge extends BridgeAbstract {
$this->items[] = $item;
}
}
public function getName(){
return (!empty($this->userName) ? $this->userName . ' - ' : '') . 'Whyd Bridge';
}

View File

@@ -9,7 +9,6 @@ class WikiLeaksBridge extends BridgeAbstract {
'category' => array(
'name' => 'Category',
'type' => 'list',
'required' => true,
'title' => 'Select your category',
'values' => array(
'News' => '-News-',
@@ -28,7 +27,6 @@ class WikiLeaksBridge extends BridgeAbstract {
'teaser' => array(
'name' => 'Show teaser',
'type' => 'checkbox',
'required' => false,
'title' => 'If checked feeds will display the teaser',
'defaultValue' => true
)

View File

@@ -13,7 +13,6 @@ class WikipediaBridge extends BridgeAbstract {
'language' => array(
'name' => 'Language',
'type' => 'list',
'required' => true,
'title' => 'Select your language',
'exampleValue' => 'English',
'values' => array(
@@ -27,7 +26,6 @@ class WikipediaBridge extends BridgeAbstract {
'subject' => array(
'name' => 'Subject',
'type' => 'list',
'required' => true,
'title' => 'What subject are you interested in?',
'exampleValue' => 'Today\'s featured article',
'values' => array(

View File

@@ -64,7 +64,6 @@ class WordPressPluginUpdateBridge extends BridgeAbstract {
}
public function getName(){
if(!is_null($this->getInput('q'))) {
return $this->getInput('q') . ' : ' . self::NAME;
@@ -76,7 +75,7 @@ class WordPressPluginUpdateBridge extends BridgeAbstract {
private function getCachedDate($url){
Debug::log('getting pubdate from url ' . $url . '');
// Initialize cache
$cache = Cache::create('FileCache');
$cache = Cache::create(Configuration::getConfig('cache', 'type'));
$cache->setPath(PATH_CACHE . 'pages/');
$params = [$url];
$cache->setParameters($params);

View File

@@ -75,8 +75,7 @@ class XenForoBridge extends BridgeAbstract {
$this->threadurl = filter_var(
$this->getInput('url'),
FILTER_VALIDATE_URL,
FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED);
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED);
if($this->threadurl === false) {
returnClientError('The URL you provided is invalid!');
@@ -458,5 +457,4 @@ class XenForoBridge extends BridgeAbstract {
return date_format($df, 'U');
}
}

99
caches/SQLiteCache.php Normal file
View File

@@ -0,0 +1,99 @@
<?php
/**
* Cache based on SQLite 3 <https://www.sqlite.org>
*/
class SQLiteCache implements CacheInterface {
protected $path;
protected $param;
private $db = null;
public function __construct() {
if (!extension_loaded('sqlite3'))
die('"sqlite3" extension not loaded. Please check "php.ini"');
$file = PATH_CACHE . 'cache.sqlite';
if (!is_file($file)) {
$this->db = new SQLite3($file);
$this->db->enableExceptions(true);
$this->db->exec("CREATE TABLE storage ('key' BLOB PRIMARY KEY, 'value' BLOB, 'updated' INTEGER)");
} else {
$this->db = new SQLite3($file);
$this->db->enableExceptions(true);
}
$this->db->busyTimeout(5000);
}
public function loadData(){
$Qselect = $this->db->prepare('SELECT value FROM storage WHERE key = :key');
$Qselect->bindValue(':key', $this->getCacheKey());
$result = $Qselect->execute();
if ($result instanceof SQLite3Result) {
$data = $result->fetchArray(SQLITE3_ASSOC);
if (isset($data['value'])) {
return unserialize($data['value']);
}
}
return null;
}
public function saveData($datas){
$Qupdate = $this->db->prepare('INSERT OR REPLACE INTO storage (key, value, updated) VALUES (:key, :value, :updated)');
$Qupdate->bindValue(':key', $this->getCacheKey());
$Qupdate->bindValue(':value', serialize($datas));
$Qupdate->bindValue(':updated', time());
$Qupdate->execute();
return $this;
}
public function getTime(){
$Qselect = $this->db->prepare('SELECT updated FROM storage WHERE key = :key');
$Qselect->bindValue(':key', $this->getCacheKey());
$result = $Qselect->execute();
if ($result instanceof SQLite3Result) {
$data = $result->fetchArray(SQLITE3_ASSOC);
if (isset($data['updated'])) {
return $data['updated'];
}
}
return false;
}
public function purgeCache($duration){
$Qdelete = $this->db->prepare('DELETE FROM storage WHERE updated < :expired');
$Qdelete->bindValue(':expired', time() - $duration);
$Qdelete->execute();
}
/**
* Set cache path
* @return self
*/
public function setPath($path){
$this->path = $path;
return $this;
}
/**
* Set HTTP GET parameters
* @return self
*/
public function setParameters(array $param){
$this->param = array_map('strtolower', $param);
return $this;
}
////////////////////////////////////////////////////////////////////////////
protected function getCacheKey(){
if(is_null($this->param)) {
throw new \Exception('Call "setParameters" first!');
}
return hash('sha1', $this->path . http_build_query($this->param), true);
}
}

View File

@@ -6,6 +6,10 @@
[cache]
; Defines the cache type used by RSS-Bridge
; "file" = FileCache (default)
type = "file"
; Allow users to specify custom timeout for specific requests.
; true = enabled
; false = disabled (default)

View File

@@ -1,22 +1,30 @@
<?php
/**
* Atom
* Documentation Source http://en.wikipedia.org/wiki/Atom_%28standard%29 and
* http://tools.ietf.org/html/rfc4287
*/
* AtomFormat - RFC 4287: The Atom Syndication Format
* https://tools.ietf.org/html/rfc4287
*
* Validator:
* https://validator.w3.org/feed/
*/
class AtomFormat extends FormatAbstract{
const LIMIT_TITLE = 140;
public function stringify(){
$https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : '';
$httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
$httpInfo = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';
$urlPrefix = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
$urlHost = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : '';
$urlPath = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : '';
$urlRequest = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : '';
$serverRequestUri = isset($_SERVER['REQUEST_URI']) ? $this->xml_encode($_SERVER['REQUEST_URI']) : '';
$feedUrl = $this->xml_encode($urlPrefix . $urlHost . $urlRequest);
$extraInfos = $this->getExtraInfos();
$title = $this->xml_encode($extraInfos['name']);
$uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : REPOSITORY;
// since we can't guarantee that all items have an author,
// a global feed author is mandatory
$feedAuthor = 'RSS-Bridge';
$uriparts = parse_url($uri);
if(!empty($extraInfos['icon'])) {
$icon = $extraInfos['icon'];
@@ -28,42 +36,79 @@ class AtomFormat extends FormatAbstract{
$entries = '';
foreach($this->getItems() as $item) {
$entryAuthor = isset($item['author']) ? $this->xml_encode($item['author']) : '';
$entryTitle = isset($item['title']) ? $this->xml_encode($item['title']) : '';
$entryUri = isset($item['uri']) ? $this->xml_encode($item['uri']) : '';
$entryTimestamp = isset($item['timestamp']) ? $this->xml_encode(date(DATE_ATOM, $item['timestamp'])) : '';
$entryContent = isset($item['content']) ? $this->xml_encode($this->sanitizeHtml($item['content'])) : '';
$entryTimestamp = $item->getTimestamp();
$entryTitle = $this->xml_encode($item->getTitle());
$entryContent = $item->getContent();
$entryUri = $item->getURI();
$entryID = '';
$entryEnclosures = '';
if(isset($item['enclosures'])) {
foreach($item['enclosures'] as $enclosure) {
$entryEnclosures .= '<link rel="enclosure" href="'
. $this->xml_encode($enclosure)
. '" type="' . getMimeType($enclosure) . '" />'
. PHP_EOL;
if (!empty($item->getUid()))
$entryID = 'urn:sha1:' . $item->getUid();
if (empty($entryID)) // Fallback to provided URI
$entryID = $this->xml_encode($entryUri);
if (empty($entryID)) // Fallback to title and content
$entryID = 'urn:sha1:' . hash('sha1', $entryTitle . $entryContent);
if (empty($entryTimestamp))
$entryTimestamp = $this->lastModified;
if (empty($entryTitle)) {
$entryTitle = str_replace("\n", ' ', strip_tags($entryContent));
if (strlen($entryTitle) > self::LIMIT_TITLE) {
$wrapPos = strpos(wordwrap($entryTitle, self::LIMIT_TITLE), "\n");
$entryTitle = substr($entryTitle, 0, $wrapPos) . '...';
}
}
if (empty($entryContent))
$entryContent = $entryTitle;
$entryAuthor = $this->xml_encode($item->getAuthor());
$entryTitle = $this->xml_encode($entryTitle);
$entryUri = $this->xml_encode($entryUri);
$entryTimestamp = $this->xml_encode(gmdate(DATE_ATOM, $entryTimestamp));
$entryContent = $this->xml_encode($this->sanitizeHtml($entryContent));
$entryEnclosures = '';
foreach($item->getEnclosures() as $enclosure) {
$entryEnclosures .= '<link rel="enclosure" href="'
. $this->xml_encode($enclosure)
. '" type="' . getMimeType($enclosure) . '" />'
. PHP_EOL;
}
$entryCategories = '';
if(isset($item['categories'])) {
foreach($item['categories'] as $category) {
$entryCategories .= '<category term="'
. $this->xml_encode($category)
. '"/>'
. PHP_EOL;
}
foreach($item->getCategories() as $category) {
$entryCategories .= '<category term="'
. $this->xml_encode($category)
. '"/>'
. PHP_EOL;
}
$entryLinkAlternate = '';
if (!empty($entryUri)) {
$entryLinkAlternate = '<link rel="alternate" type="text/html" href="'
. $entryUri
. '"/>';
}
if (!empty($entryAuthor)) {
$entryAuthor = '<author><name>'
. $entryAuthor
. '</name></author>';
}
$entries .= <<<EOD
<entry>
<author>
<name>{$entryAuthor}</name>
</author>
<title type="html">{$entryTitle}</title>
<link rel="alternate" type="text/html" href="{$entryUri}" />
<id>{$entryUri}</id>
<published>{$entryTimestamp}</published>
<updated>{$entryTimestamp}</updated>
<id>{$entryID}</id>
{$entryLinkAlternate}
{$entryAuthor}
<content type="html">{$entryContent}</content>
{$entryEnclosures}
{$entryCategories}
@@ -72,21 +117,24 @@ class AtomFormat extends FormatAbstract{
EOD;
}
$feedTimestamp = date(DATE_ATOM, time());
$charset = $this->getCharset();
$feedTimestamp = gmdate(DATE_ATOM, $this->lastModified);
$charset = $this->getCharset();
/* Data are prepared, now let's begin the "MAGIE !!!" */
$toReturn = <<<EOD
<?xml version="1.0" encoding="{$charset}"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0">
<feed xmlns="http://www.w3.org/2005/Atom">
<title type="text">{$title}</title>
<id>http{$https}://{$httpHost}{$httpInfo}/</id>
<id>{$feedUrl}</id>
<icon>{$icon}</icon>
<logo>{$icon}</logo>
<updated>{$feedTimestamp}</updated>
<author>
<name>{$feedAuthor}</name>
</author>
<link rel="alternate" type="text/html" href="{$uri}" />
<link rel="self" href="http{$https}://{$httpHost}{$serverRequestUri}" />
<link rel="self" type="application/atom+xml" href="{$feedUrl}" />
{$entries}
</feed>
EOD;

View File

@@ -1,6 +1,5 @@
<?php
class HtmlFormat extends FormatAbstract {
public function stringify(){
$extraInfos = $this->getExtraInfos();
$title = htmlspecialchars($extraInfos['name']);
@@ -10,31 +9,31 @@ class HtmlFormat extends FormatAbstract {
$entries = '';
foreach($this->getItems() as $item) {
$entryAuthor = isset($item['author']) ? '<br /><p class="author">by: ' . $item['author'] . '</p>' : '';
$entryTitle = isset($item['title']) ? $this->sanitizeHtml(strip_tags($item['title'])) : '';
$entryUri = isset($item['uri']) ? $item['uri'] : $uri;
$entryAuthor = $item->getAuthor() ? '<br /><p class="author">by: ' . $item->getAuthor() . '</p>' : '';
$entryTitle = $this->sanitizeHtml(strip_tags($item->getTitle()));
$entryUri = $item->getURI() ?: $uri;
$entryTimestamp = '';
if(isset($item['timestamp'])) {
if($item->getTimestamp()) {
$entryTimestamp = '<time datetime="'
. date(DATE_ATOM, $item['timestamp'])
. date(DATE_ATOM, $item->getTimestamp())
. '">'
. date(DATE_ATOM, $item['timestamp'])
. date(DATE_ATOM, $item->getTimestamp())
. '</time>';
}
$entryContent = '';
if(isset($item['content'])) {
if($item->getContent()) {
$entryContent = '<div class="content">'
. $this->sanitizeHtml($item['content'])
. $this->sanitizeHtml($item->getContent())
. '</div>';
}
$entryEnclosures = '';
if(isset($item['enclosures'])) {
if(!empty($item->getEnclosures())) {
$entryEnclosures = '<div class="attachments"><p>Attachments:</p>';
foreach($item['enclosures'] as $enclosure) {
foreach($item->getEnclosures() as $enclosure) {
$url = $this->sanitizeHtml($enclosure);
$entryEnclosures .= '<li class="enclosure"><a href="'
@@ -48,10 +47,10 @@ class HtmlFormat extends FormatAbstract {
}
$entryCategories = '';
if(isset($item['categories']) && count($item['categories']) > 0) {
if(!empty($item->getCategories())) {
$entryCategories = '<div class="categories"><p>Categories:</p>';
foreach($item['categories'] as $category) {
foreach($item->getCategories() as $category) {
$entryCategories .= '<li class="category">'
. $this->sanitizeHtml($category)

View File

@@ -1,13 +1,115 @@
<?php
/**
* Json
* Builds a JSON string from $this->items and return it to browser.
*/
* JsonFormat - JSON Feed Version 1
* https://jsonfeed.org/version/1
*
* Validators:
* https://validator.jsonfeed.org
* https://github.com/vigetlabs/json-feed-validator
*/
class JsonFormat extends FormatAbstract {
const VENDOR_EXCLUDES = array(
'author',
'title',
'uri',
'timestamp',
'content',
'enclosures',
'categories',
'uid',
);
public function stringify(){
$items = $this->getItems();
$toReturn = json_encode($items, JSON_PRETTY_PRINT);
$urlPrefix = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
$urlHost = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : '';
$urlPath = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : '';
$urlRequest = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : '';
$extraInfos = $this->getExtraInfos();
$data = array(
'version' => 'https://jsonfeed.org/version/1',
'title' => (!empty($extraInfos['name'])) ? $extraInfos['name'] : $urlHost,
'home_page_url' => (!empty($extraInfos['uri'])) ? $extraInfos['uri'] : REPOSITORY,
'feed_url' => $urlPrefix . $urlHost . $urlRequest
);
if (!empty($extraInfos['icon'])) {
$data['icon'] = $extraInfos['icon'];
$data['favicon'] = $extraInfos['icon'];
}
$items = array();
foreach ($this->getItems() as $item) {
$entry = array();
$entryAuthor = $item->getAuthor();
$entryTitle = $item->getTitle();
$entryUri = $item->getURI();
$entryTimestamp = $item->getTimestamp();
$entryContent = $this->sanitizeHtml($item->getContent());
$entryEnclosures = $item->getEnclosures();
$entryCategories = $item->getCategories();
$vendorFields = $item->toArray();
foreach (self::VENDOR_EXCLUDES as $key) {
unset($vendorFields[$key]);
}
$entry['id'] = $item->getUid();
if (empty($entry['id'])) {
$entry['id'] = $entryUri;
}
if (!empty($entryTitle)) {
$entry['title'] = $entryTitle;
}
if (!empty($entryAuthor)) {
$entry['author'] = array(
'name' => $entryAuthor
);
}
if (!empty($entryTimestamp)) {
$entry['date_modified'] = gmdate(DATE_ATOM, $entryTimestamp);
}
if (!empty($entryUri)) {
$entry['url'] = $entryUri;
}
if (!empty($entryContent)) {
if ($this->isHTML($entryContent)) {
$entry['content_html'] = $entryContent;
} else {
$entry['content_text'] = $entryContent;
}
}
if (!empty($entryEnclosures)) {
$entry['attachments'] = array();
foreach ($entryEnclosures as $enclosure) {
$entry['attachments'][] = array(
'url' => $enclosure,
'mime_type' => getMimeType($enclosure)
);
}
}
if (!empty($entryCategories)) {
$entry['tags'] = array();
foreach ($entryCategories as $category) {
$entry['tags'][] = $category;
}
}
if (!empty($vendorFields)) {
$entry['_rssbridge'] = $vendorFields;
}
if (empty($entry['id']))
$entry['id'] = hash('sha1', $entryTitle . $entryContent);
$items[] = $entry;
}
$data['items'] = $items;
$toReturn = json_encode($data, JSON_PRETTY_PRINT);
// Remove invalid non-UTF8 characters
ini_set('mbstring.substitute_character', 'none');
@@ -22,4 +124,8 @@ class JsonFormat extends FormatAbstract {
return parent::display();
}
private function isHTML($text) {
return (strlen(strip_tags($text)) != strlen($text));
}
}

View File

@@ -1,19 +1,45 @@
<?php
/**
* Mrss
* Documentation Source http://www.rssboard.org/media-rss
*/
* MrssFormat - RSS 2.0 + Media RSS
* http://www.rssboard.org/rss-specification
* http://www.rssboard.org/media-rss
*
* Validators:
* https://validator.w3.org/feed/
* http://www.rssboard.org/rss-validator/
*
* Notes about the implementation:
*
* - The item author is not supported as it needs to be an e-mail address to be
* valid.
* - The RSS specification does not explicitly allow to have more than one
* enclosure as every item is meant to provide one "story", thus having
* multiple enclosures per item may lead to unexpected behavior.
* On top of that, it requires to have a length specified, which RSS-Bridge
* can't provide.
* - The Media RSS extension comes in handy, since it allows to have multiple
* enclosures, even though they recommend to have only one enclosure because
* of the one-story-per-item reason. It only requires to specify the URL,
* everything else is optional.
* - Since the Media RSS extension has its own namespace, the output is a valid
* RSS 2.0 feed that works with feed readers that don't support the extension.
*/
class MrssFormat extends FormatAbstract {
const ALLOWED_IMAGE_EXT = array(
'.gif', '.jpg', '.png'
);
public function stringify(){
$https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : '';
$httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
$httpInfo = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';
$urlPrefix = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
$urlHost = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : '';
$urlPath = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : '';
$urlRequest = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : '';
$serverRequestUri = isset($_SERVER['REQUEST_URI']) ? $this->xml_encode($_SERVER['REQUEST_URI']) : '';
$feedUrl = $this->xml_encode($urlPrefix . $urlHost . $urlRequest);
$extraInfos = $this->getExtraInfos();
$title = $this->xml_encode($extraInfos['name']);
$icon = $extraInfos['icon'];
if(!empty($extraInfos['uri'])) {
$uri = $this->xml_encode($extraInfos['uri']);
@@ -21,55 +47,65 @@ class MrssFormat extends FormatAbstract {
$uri = REPOSITORY;
}
$uriparts = parse_url($uri);
$icon = $this->xml_encode($uriparts['scheme'] . '://' . $uriparts['host'] . '/favicon.ico');
$items = '';
foreach($this->getItems() as $item) {
$itemAuthor = isset($item['author']) ? $this->xml_encode($item['author']) : '';
$itemTitle = strip_tags(isset($item['title']) ? $this->xml_encode($item['title']) : '');
$itemUri = isset($item['uri']) ? $this->xml_encode($item['uri']) : '';
$itemTimestamp = isset($item['timestamp']) ? $this->xml_encode(date(DATE_RFC2822, $item['timestamp'])) : '';
$itemContent = isset($item['content']) ? $this->xml_encode($this->sanitizeHtml($item['content'])) : '';
$itemTimestamp = $item->getTimestamp();
$itemTitle = $this->xml_encode($item->getTitle());
$itemUri = $this->xml_encode($item->getURI());
$itemContent = $this->xml_encode($this->sanitizeHtml($item->getContent()));
$entryID = $item->getUid();
$isPermaLink = 'false';
if (empty($entryID) && !empty($itemUri)) { // Fallback to provided URI
$entryID = $itemUri;
$isPermaLink = 'true';
}
if (empty($entryID)) // Fallback to title and content
$entryID = hash('sha1', $itemTitle . $itemContent);
$entryTitle = '';
if (!empty($itemTitle))
$entryTitle = '<title>' . $itemTitle . '</title>';
$entryLink = '';
if (!empty($itemUri))
$entryLink = '<link>' . $itemUri . '</link>';
$entryPublished = '';
if (!empty($itemTimestamp)) {
$entryPublished = '<pubDate>'
. $this->xml_encode(gmdate(DATE_RFC2822, $itemTimestamp))
. '</pubDate>';
}
$entryDescription = '';
if (!empty($itemContent))
$entryDescription = '<description>' . $itemContent . '</description>';
$entryEnclosuresWarning = '';
$entryEnclosures = '';
if(isset($item['enclosures'])) {
$entryEnclosures .= '<enclosure url="'
. $this->xml_encode($item['enclosures'][0])
. '" type="' . getMimeType($item['enclosures'][0]) . '" />';
if(count($item['enclosures']) > 1) {
$entryEnclosures .= PHP_EOL;
$entryEnclosuresWarning = '&lt;br&gt;Warning:
Some media files might not be shown to you. Consider using the ATOM format instead!';
foreach($item['enclosures'] as $enclosure) {
$entryEnclosures .= '<atom:link rel="enclosure" href="'
. $enclosure . '" type="' . getMimeType($enclosure) . '" />'
. PHP_EOL;
}
}
foreach($item->getEnclosures() as $enclosure) {
$entryEnclosures .= '<media:content url="'
. $this->xml_encode($enclosure)
. '" type="' . getMimeType($enclosure) . '"/>'
. PHP_EOL;
}
$entryCategories = '';
if(isset($item['categories'])) {
foreach($item['categories'] as $category) {
$entryCategories .= '<category>'
. $category . '</category>'
. PHP_EOL;
}
foreach($item->getCategories() as $category) {
$entryCategories .= '<category>'
. $category . '</category>'
. PHP_EOL;
}
$items .= <<<EOD
<item>
<title>{$itemTitle}</title>
<link>{$itemUri}</link>
<guid isPermaLink="true">{$itemUri}</guid>
<pubDate>{$itemTimestamp}</pubDate>
<description>{$itemContent}{$entryEnclosuresWarning}</description>
<author>{$itemAuthor}</author>
{$entryTitle}
{$entryLink}
<guid isPermaLink="{$isPermaLink}">{$entryID}</guid>
{$entryPublished}
{$entryDescription}
{$entryEnclosures}
{$entryCategories}
</item>
@@ -79,22 +115,28 @@ EOD;
$charset = $this->getCharset();
/* xml attributes need to have certain characters escaped to be w3c compliant */
$imageTitle = htmlspecialchars($title, ENT_COMPAT);
$feedImage = '';
if (!empty($icon) && in_array(substr($icon, -4), self::ALLOWED_IMAGE_EXT)) {
$feedImage .= <<<EOD
<image>
<url>{$icon}</url>
<title>{$title}</title>
<link>{$uri}</link>
</image>
EOD;
}
/* Data are prepared, now let's begin the "MAGIE !!!" */
$toReturn = <<<EOD
<?xml version="1.0" encoding="{$charset}"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:media="http://search.yahoo.com/mrss/"
xmlns:atom="http://www.w3.org/2005/Atom">
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{$title}</title>
<link>http{$https}://{$httpHost}{$httpInfo}/</link>
<link>{$uri}</link>
<description>{$title}</description>
<image url="{$icon}" title="{$imageTitle}" link="{$uri}"/>
<atom:link rel="alternate" type="text/html" href="{$uri}" />
<atom:link rel="self" href="http{$https}://{$httpHost}{$serverRequestUri}" />
{$feedImage}
<atom:link rel="alternate" type="text/html" href="{$uri}"/>
<atom:link rel="self" href="{$feedUrl}" type="application/atom+xml"/>
{$items}
</channel>
</rss>

View File

@@ -4,10 +4,15 @@
* Returns $this->items as raw php data.
*/
class PlaintextFormat extends FormatAbstract {
public function stringify(){
$items = $this->getItems();
$toReturn = print_r($items, true);
$data = array();
foreach($items as $item) {
$data[] = $item->toArray();
}
$toReturn = print_r($data, true);
// Remove invalid non-UTF8 characters
ini_set('mbstring.substitute_character', 'none');

262
index.php
View File

@@ -51,263 +51,15 @@ $whitelist_default = array(
try {
Bridge::setWhitelist($whitelist_default);
$actionFac = new \ActionFactory();
$actionFac->setWorkingDir(PATH_LIB_ACTIONS);
$showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN);
$action = array_key_exists('action', $params) ? $params['action'] : null;
$bridge = array_key_exists('bridge', $params) ? $params['bridge'] : null;
// Return list of bridges as JSON formatted text
if($action === 'list') {
$list = new StdClass();
$list->bridges = array();
$list->total = 0;
foreach(Bridge::getBridgeNames() as $bridgeName) {
$bridge = Bridge::create($bridgeName);
if($bridge === false) { // Broken bridge, show as inactive
$list->bridges[$bridgeName] = array(
'status' => 'inactive'
);
continue;
}
$status = Bridge::isWhitelisted($bridgeName) ? 'active' : 'inactive';
$list->bridges[$bridgeName] = array(
'status' => $status,
'uri' => $bridge->getURI(),
'name' => $bridge->getName(),
'icon' => $bridge->getIcon(),
'parameters' => $bridge->getParameters(),
'maintainer' => $bridge->getMaintainer(),
'description' => $bridge->getDescription()
);
}
$list->total = count($list->bridges);
header('Content-Type: application/json');
echo json_encode($list, JSON_PRETTY_PRINT);
} 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!');
// DEPRECATED: 'nameFormat' scheme is replaced by 'name' in format parameter values
// this is to keep compatibility until futher complete removal
if(($pos = strpos($format, 'Format')) === (strlen($format) - strlen('Format'))) {
$format = substr($format, 0, $pos);
}
// whitelist control
if(!Bridge::isWhitelisted($bridge)) {
throw new \Exception('This bridge is not whitelisted', 401);
die;
}
// Data retrieval
$bridge = Bridge::create($bridge);
$noproxy = array_key_exists('_noproxy', $params) && filter_var($params['_noproxy'], FILTER_VALIDATE_BOOLEAN);
if(defined('PROXY_URL') && PROXY_BYBRIDGE && $noproxy) {
define('NOPROXY', true);
}
// Cache timeout
$cache_timeout = -1;
if(array_key_exists('_cache_timeout', $params)) {
if(!CUSTOM_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);
} else {
$cache_timeout = $bridge->getCacheTimeout();
}
// Remove parameters that don't concern bridges
$bridge_params = array_diff_key(
$params,
array_fill_keys(
array(
'action',
'bridge',
'format',
'_noproxy',
'_cache_timeout',
'_error_time'
), '')
);
// Remove parameters that don't concern caches
$cache_params = array_diff_key(
$params,
array_fill_keys(
array(
'action',
'format',
'_noproxy',
'_cache_timeout',
'_error_time'
), '')
);
// Initialize cache
$cache = Cache::create('FileCache');
$cache->setPath(PATH_CACHE);
$cache->purgeCache(86400); // 24 hours
$cache->setParameters($cache_params);
$items = array();
$infos = array();
$mtime = $cache->getTime();
if($mtime !== false
&& (time() - $cache_timeout < $mtime)
&& !Debug::isEnabled()) { // Load cached data
// Send "Not Modified" response if client supports it
// Implementation based on https://stackoverflow.com/a/10847262
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
if($mtime <= $stime) { // Cached data is older or same
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304);
die();
}
}
$cached = $cache->loadData();
if(isset($cached['items']) && isset($cached['extraInfos'])) {
$items = $cached['items'];
$infos = $cached['extraInfos'];
}
} else { // Collect new data
try {
$bridge->setDatas($bridge_params);
$bridge->collectData();
$items = $bridge->getItems();
$infos = array(
'name' => $bridge->getName(),
'uri' => $bridge->getURI(),
'icon' => $bridge->getIcon()
);
} catch(Error $e) {
error_log($e);
$item = array();
// Create "new" error message every 24 hours
$params['_error_time'] = urlencode((int)(time() / 86400));
// Error 0 is a special case (i.e. "trying to get property of non-object")
if($e->getCode() === 0) {
$item['title'] = 'Bridge encountered an unexpected situation! (' . $params['_error_time'] . ')';
} else {
$item['title'] = 'Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')';
}
$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'] = (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);
$items[] = $item;
}
// Store data in cache
$cache->saveData(array(
'items' => $items,
'extraInfos' => $infos
));
}
// Data transformation
try {
$format = Format::create($format);
$format->setItems($items);
$format->setExtraInfos($infos);
$format->setLastModified($cache->getTime());
$format->display();
} catch(Error $e) {
error_log($e);
header('Content-Type: text/html', true, $e->getCode());
die(buildTransformException($e, $bridge));
} catch(Exception $e) {
error_log($e);
header('Content-Type: text/html', true, $e->getCode());
die(buildTransformException($e, $bridge));
}
if(array_key_exists('action', $params)) {
$action = $actionFac->create($params['action']);
$action->setUserData($params);
$action->execute();
} else {
$showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN);
echo BridgeList::create($showInactive);
}
} catch(\Exception $e) {

33
lib/ActionAbstract.php Normal file
View File

@@ -0,0 +1,33 @@
<?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 action objects
*/
abstract class ActionAbstract implements ActionInterface {
/**
* Holds the user data.
*
* @var array
*/
protected $userData = null;
/**
* {@inheritdoc}
*
* @param array $userData {@inheritdoc}
*/
public function setUserData($userData) {
$this->userData = $userData;
}
}

65
lib/ActionFactory.php Normal file
View File

@@ -0,0 +1,65 @@
<?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 for action objects.
*/
class ActionFactory extends FactoryAbstract {
/**
* {@inheritdoc}
*
* @param string $name {@inheritdoc}
*/
public function create($name) {
$filePath = $this->buildFilePath($name);
if(!file_exists($filePath)) {
throw new \Exception('File ' . $filePath . ' does not exist!');
}
require_once $filePath;
$class = $this->buildClassName($name);
if((new \ReflectionClass($class))->isInstantiable()) {
return new $class();
}
return false;
}
/**
* Build class name from action name
*
* The class name consists of the action name with prefix "Action". The first
* character of the class name must be uppercase.
*
* Example: 'display' => 'DisplayAction'
*
* @param string $name The action name.
* @return string The class name.
*/
protected function buildClassName($name) {
return ucfirst(strtolower($name)) . 'Action';
}
/**
* Build file path to the action class.
*
* @param string $name The action name.
* @return string Path to the action class.
*/
protected function buildFilePath($name) {
return $this->getWorkingDir() . $this->buildClassName($name) . '.php';
}
}

34
lib/ActionInterface.php Normal file
View File

@@ -0,0 +1,34 @@
<?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
*/
/**
* Interface for action objects.
*/
interface ActionInterface {
/**
* Set user data for the action to consume.
*
* @param array $userData An associative array of user data.
* @return void
*/
function setUserData($userData);
/**
* Execute the action.
*
* Note: This function directly outputs data to the user.
*
* @return void
*/
function execute();
}

View File

@@ -31,7 +31,6 @@
* 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!
@@ -83,5 +82,4 @@ class Authentication {
return false;
}
}

View File

@@ -213,7 +213,7 @@ class Bridge {
// Create initial whitelist or load from disk
if (!file_exists(WHITELIST) && !empty(self::$whitelist)) {
file_put_contents(WHITELIST, implode("\n", self::$whitelist));
} else {
} elseif(file_exists(WHITELIST)) {
$contents = trim(file_get_contents(WHITELIST));

View File

@@ -290,5 +290,4 @@ abstract class BridgeAbstract implements BridgeInterface {
return null;
}
}
}

View File

@@ -20,7 +20,6 @@
* @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
*
@@ -208,6 +207,11 @@ This bridge is not fetching its content through a secure connection</div>';
* @return string The list input field
*/
private static function getListInput($entry, $id, $name) {
if(isset($entry['required']) && $entry['required'] === true) {
Debug::log('The "required" attribute is not supported for lists.');
unset($entry['required']);
}
$list = '<select '
. self::getInputAttributes($entry)
. ' id="'
@@ -268,6 +272,11 @@ This bridge is not fetching its content through a secure connection</div>';
* @return string The checkbox input field
*/
private static function getCheckboxInput($entry, $id, $name) {
if(isset($entry['required']) && $entry['required'] === true) {
Debug::log('The "required" attribute is not supported for checkboxes.');
unset($entry['required']);
}
return '<input '
. self::getInputAttributes($entry)
. ' id="'

View File

@@ -53,7 +53,6 @@
* The default cache timeout for the bridge.
*/
interface BridgeInterface {
/**
* Collects data from the site
*/

View File

@@ -20,7 +20,6 @@
* @todo Return error if a caller creates an object of this class.
*/
final class BridgeList {
/**
* Get the document head
*

View File

@@ -64,6 +64,8 @@ class Cache {
* @return object|bool The cache object or false if the class is not instantiable.
*/
public static function create($name){
$name = self::sanitizeCacheName($name) . 'Cache';
if(!self::isCacheName($name)) {
throw new \InvalidArgumentException('Cache name invalid!');
}
@@ -137,4 +139,75 @@ class Cache {
public static function isCacheName($name){
return is_string($name) && preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name) === 1;
}
/**
* Returns a list of cache names from the working directory.
*
* The list is cached internally to allow for successive calls.
*
* @return array List of cache names
*/
public static function getCacheNames(){
static $cacheNames = array(); // Initialized on first call
if(empty($cacheNames)) {
$files = scandir(self::getWorkingDir());
if($files !== false) {
foreach($files as $file) {
if(preg_match('/^([^.]+)Cache\.php$/U', $file, $out)) {
$cacheNames[] = $out[1];
}
}
}
}
return $cacheNames;
}
/**
* Returns the sanitized cache name.
*
* The cache name can be specified in various ways:
* * The PHP file name (i.e. `FileCache.php`)
* * The PHP file name without file extension (i.e. `FileCache`)
* * The cache name (i.e. `file`)
*
* Casing is ignored (i.e. `FILE` and `fIlE` are the same).
*
* A cache file matching the given cache name must exist in the working
* directory!
*
* @param string $name The cache name
* @return string|null The sanitized cache name if the provided name is
* valid, null otherwise.
*/
protected static function sanitizeCacheName($name) {
if(is_string($name)) {
// Trim trailing '.php' if exists
if(preg_match('/(.+)(?:\.php)/', $name, $matches)) {
$name = $matches[1];
}
// Trim trailing 'Cache' if exists
if(preg_match('/(.+)(?:Cache)$/i', $name, $matches)) {
$name = $matches[1];
}
// The name is valid if a corresponding file is found on disk
if(in_array(strtolower($name), array_map('strtolower', self::getCacheNames()))) {
$index = array_search(strtolower($name), array_map('strtolower', self::getCacheNames()));
return self::getCacheNames()[$index];
}
Debug::log('Invalid cache name specified: "' . $name . '"!');
}
return null; // Bad parameter
}
}

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