1
0
mirror of https://github.com/RSS-Bridge/rss-bridge.git synced 2025-08-16 13:34:11 +02:00

Compare commits

...

280 Commits

Author SHA1 Message Date
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
logmanoriginal
a11ade3442 Bump version to 2018-12-11 2018-12-11 17:01:16 +01:00
logmanoriginal
3932e7b8ef [README] Update list of contributors
Fix links pointing to the API instead of HTML pages
2018-12-10 22:21:33 +01:00
disk0x
5305c405f6 [SoundcloudBridge] Improve Author, Date, Description (#955)
1. Author Name now doesn't include Episode Title
2. It now fetches Episode Creation Timestamp, to allow correct sorting in podcatchers
3. Description is now the actual show notes, and not an <audio> tag
2018-12-10 21:35:18 +01:00
triatic
1c58c04271 [contents] Better error reporting for cUrl errors (#958)
References #954
2018-12-10 21:20:13 +01:00
logmanoriginal
89218f1da6 [.travis.yml] Fix broken checks
- Remove "sudo:false"
- Update composer installation paths

The Linux infrastructure migration removed support for "sudo:false"

-- https://changelog.travis-ci.com/deprecation-container-based-linux-build-environment-82037
-- https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration
2018-12-07 18:52:37 +01:00
disk0x
30e2b79c38 [SoundcloudBridge] Add RSS enclosures (#952)
Minimum viable code change to get SoundcloudBridge produce feeds that podcatchers like gPodder can understand.
2018-12-04 16:16:19 +01:00
Nono
2184f523cd [MozillaSecurity] New Bridge (#946)
* [MozillaSecurity] New Bridge

Kudo to @teromene & @ArthurHoaro on this one !
2018-11-30 18:25:02 +01:00
triatic
242b6953ed [FB2Bridge] Adapt to Facebook html change (#950) 2018-11-30 18:23:37 +01:00
Roliga
bdcb7a9829 [index] Fix detect action after listBridges rename (#947)
Commit 88b0656 renamed listBridges function which was not taken into
account when adding the detect action.
2018-11-29 16:44:38 +01:00
Pierre Mazière
f4b46e497e [GithubIssueBridge] Be consistent in avoiding is_null
Signed-off-by: Pierre Mazière <pierre.maziere@gmx.com>
2018-11-29 16:35:49 +01:00
Pierre Mazière
d5085a4116 [GithubIssueBridge] Fix non existing comments count
Signed-off-by: Pierre Mazière <pierre.maziere@gmx.com>
2018-11-29 16:35:45 +01:00
Pierre Mazière
d7cabfca54 [GithubIssueBridge] Fix issue comments and events parsing
Signed-off-by: Pierre Mazière <pierre.maziere@gmx.com>
2018-11-29 16:35:41 +01:00
Pierre Mazière
de575982a1 [GithubIssueBridge] Fix most relevant coding style related issues
Signed-off-by: Pierre Mazière <pierre.maziere@gmx.com>
2018-11-29 16:35:35 +01:00
LogMANOriginal
3d301fc4ee [contents] Skip caching if the remote server requests no caching (#945)
* Skip caching if Cache-Control defines no-cache
* Skip caching if Cache-Control defines no-store
2018-11-28 17:36:28 +01:00
triatic
263e8872ea core: Don't use server variables in CLI mode (#939) 2018-11-26 18:33:51 +01:00
logmanoriginal
6e9c188a72 [GlassdoorBridge] Fix bridge is marked as executable
References #938
2018-11-26 18:31:25 +01:00
Roliga
49da67cb33 core: Automatically select a bridge based on a URL (#928)
* core: Add bridge parameter auto detection

This adds a new 'detect' action which accepts a URL from which an
appropriate bridge is selected and relevant parameters are extracted.
The user is then automatically redirected to the selected bridge.

For example to get a feed from: https://twitter.com/search?q=%23rss-bridge
we could send a request to:
'/?action=detect&format=Atom&url=twitter.com/search%3Fq%3D%2523rss-bridge'
which would redirect to:
'/?action=display&q=%23rss-bridge&bridge=Twitter&format=Atom'.

This auto detection happens on a per-bridge basis, so a new function
'detectParameters' is added to BridgeInterface which bridges may implement.
It takes a URL for an argument and returns a list of parameters that were
extracted, or null if the URL isn't relevant for the bridge.

* [TwitterBridge] Add parameter auto detection

* [BridgeAbstract] Add generic parameter detection

This adds generic "paramater detection" for bridges that don't have any
parameters defined. If the queried URL matches the URI defined in the
bridge (ignoring https://, www. and trailing /) an emtpy list of parameters is
returned.
2018-11-26 18:05:40 +01:00
sysadminstory
b4dbd191d0 [ZoneTelechargementBridge] Switch to the new Website (#934)
* [ZoneTelechargementBridge] Switch to the new Website

The website zone-telechargement1.org decided that he will be using a new
domain at the end of november :
https://www.annuaire-telechargement.com/

The bridge uses the new domain but still uses the same filename and
class name to keep the existing feed working.
2018-11-20 16:23:17 +01:00
logmanoriginal
e09f452426 [.gitattributes] Exclude files from git archive
Files with the option "export-ignore" are excluded from "git archive"
commands. Release files from GitHub will also ignore those files, so
packages are smaller and don't include unneccessary files.
2018-11-19 18:11:09 +01:00
LogMANOriginal
7b261d1cc2 [contents] Add server side caching for all requests (If-Modified-Since) (#889)
This commit adds a cache for 'getContents' to '/cache/server'. All
contents are cached by default (even in debug mode). If debug mode
is enabled, the cached data is overwritten on each request.

In normal mode RSS-Bridge adds the 'If-Modified-Since' header with
the timestamp from the previously cached data (if available) to the
request.

Find more information on 'If-Modified-Since' here:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since

If the server responds with "304 Not Modified", the cached data is
returned.

If the server responds with "200 OK", the received data is written
to the cache (creates a new cache file if it doesn't exist yet).

No changes were made for all other response codes.

Servers that don't support the 'If-Modified-Since' header, will
respond with "200 OK".

For servers that respond with "304 Not Modified", the required band-
width will decrease and RSS-Bridge will responding faster.

Files in the cache are forcefully removed after 24 hours.

Notice: Only few servers actually do support 'If-Modified-Since'.
Thus, most bridges won't be affected by this change.
2018-11-19 17:53:08 +01:00
logmanoriginal
96a518c9e7 [html] Remove todo as it is already implemented 2018-11-18 17:52:45 +01:00
logmanoriginal
0d2ea9a677 [html] Rename parameters for sanitize() 2018-11-18 17:43:34 +01:00
logmanoriginal
66e82e46db [html] Remove todo tags
It is not feasible to use a single 'substr' in the functions
2018-11-18 17:36:00 +01:00
logmanoriginal
54800fcc8d [html] Clarify meaning of strange find() parameter
simple_html_dom currently doesnt support "->find('*')", which is a
known issue: https://sourceforge.net/p/simplehtmldom/bugs/157/

The solution implemented by RSS-Bridge is to find all nodes WITHOUT
a specific attribute. If the attribute is very unlikely to appear
in the DOM, this is essentially returning all nodes.

This is the meaning behind

"->find('*[!b38fd2b1fe7f4747d6b1c1254ccd055e]')"
2018-11-18 17:32:07 +01:00
logmanoriginal
67004556e6 [BridgeCard] Use self:: instead of BridgeCard:: 2018-11-18 16:59:13 +01:00
logmanoriginal
c6a7b9ac64 exception: Remove HttpException class
This class served no particular purpose (other than adding a
layer on top of Exception).
2018-11-18 16:53:21 +01:00
logmanoriginal
dbffbd4d4e [FormatAbstract] Check content type before sending header 2018-11-18 16:30:34 +01:00
logmanoriginal
1c17ffb5c4 [FeedExpander] Add constants for feed types 2018-11-18 16:18:40 +01:00
logmanoriginal
326cfb21cf [FeedExpander] Rename $name to $title 2018-11-18 16:11:38 +01:00
logmanoriginal
8ab1fb86a9 [FeedExpander] Let collectExpandableDatas() return self 2018-11-18 16:03:32 +01:00
logmanoriginal
a9ec3d0d1f [Configuration] Change scope of $config to private 2018-11-18 15:58:34 +01:00
logmanoriginal
ac5bcb62ec [Configuration] Add documentation for defined constants 2018-11-18 15:52:28 +01:00
logmanoriginal
f24ab8b51b [Configuration] Rename $category to $section in getConfig() 2018-11-18 15:45:17 +01:00
logmanoriginal
4348119adf [Configuration] Make file paths explicit 2018-11-18 15:41:43 +01:00
logmanoriginal
fd4124cda2 [Configuration] Make class final
This class is essential to the core library of RSS-Bridge and must
not be extended. This improves predictability for the behaviour of
this class.
2018-11-18 15:34:16 +01:00
logmanoriginal
91f7405297 [Configuration] Throw exception creating objects of this class
This class only provides static functions.
2018-11-18 15:29:50 +01:00
logmanoriginal
85685b7758 [Authentication] Throw exception creating objects from this class
Callers must use Authentication::showPromptIfNeeded()
2018-11-18 15:20:43 +01:00
logmanoriginal
41d02554f3 [YGGTorrentBridge] Add URI to feed items
References #931
2018-11-18 09:41:14 +01:00
logmanoriginal
c4550be812 lib: Add API documentation 2018-11-18 09:41:14 +01:00
Thibault Couraud
b29ba5b973 [CrewbayBridge] Update bridge according to new crewbay.com website (#930) 2018-11-18 09:16:24 +01:00
logmanoriginal
254fe9212a [Debug] Fix debug mode reports indexing error
Error log reports "PHP Notice:  Undefined offset: 2 in /rss-bridge/
lib/Debug.php on line 112" if the array returned by debug_backtrace
does not contain 3 items.

This commit fixes the issue by always using the last element in the
backtrace "end($backtrace)".
2018-11-16 20:19:52 +01:00
triatic
3806895059 [FacebookBridge] Improve titles (#924)
A slightly improved version of #454 and #468 . Build titles from content rather than author + pre-content (which doesn't reflect anything useful).
2018-11-16 15:33:54 +01:00
triatic
599d438a0d [FacebookBridge] Decode all elements in $item (#925) 2018-11-16 15:25:58 +01:00
triatic
e5a6baab96 [TwitterBridge] Decode HTML entities (#926)
Removes duplicate encoding like &amp;quot; (should be &quot;).
2018-11-15 22:00:01 +01:00
logmanoriginal
b47a30ecc1 [rssbridge] Improve documentation 2018-11-15 20:52:17 +01:00
logmanoriginal
860b36c1e3 [Debug] Use self:: instead of Debug:: inside the class 2018-11-15 20:28:26 +01:00
logmanoriginal
3d475572c6 [Debug] Improve documentation 2018-11-15 20:27:32 +01:00
logmanoriginal
59f2d755fe format: Refactor searchInformation
- Rename function to getFormatName
- Add documentation
- Rename variables
- Remove unused variables
2018-11-15 20:16:21 +01:00
logmanoriginal
d7c374bd8c [Format] Add function isFormatName
Returns true if the provided format name is valid
2018-11-15 20:14:43 +01:00
logmanoriginal
6b6ab6486a [Format] Store real path to working directory 2018-11-15 20:06:45 +01:00
logmanoriginal
6c4e239f64 format: Refactor class Format 2018-11-15 20:06:23 +01:00
logmanoriginal
88b0656954 bridge: Rename listBridge to getBridgeNames 2018-11-15 19:43:23 +01:00
logmanoriginal
66b11b8c41 [Bridge] Fix typo 2018-11-15 19:38:14 +01:00
logmanoriginal
1b34d9860e [Cache] Check if class is instantiable 2018-11-15 19:36:01 +01:00
logmanoriginal
6e70d461e1 [Bridge] Add function isBridgeName
This function returns true if the provided name is a valid
bridge name.
2018-11-15 19:33:56 +01:00
logmanoriginal
0a92b5d29b [Bridge] Refactor Bridge::create to improve readability 2018-11-15 19:31:31 +01:00
logmanoriginal
e3849f45ab [Bridge] Use slashes to enclose regex 2018-11-15 19:30:33 +01:00
logmanoriginal
3d9c4a3718 [Bridge] Improve working directory handling
- Initialize with null to prevent leaking configurations
- Check if the working directory is a directory
- Store the real path instead of raw data
- Add final path separator as expected by Bridge::create
2018-11-15 19:28:56 +01:00
logmanoriginal
5f146a257e [Bridge] Change visibility from private to protected 2018-11-15 19:24:43 +01:00
logmanoriginal
936688e08c [Bridge] Fix typos 2018-11-15 19:22:32 +01:00
logmanoriginal
4b5372638c [Bridge] Use self:: instead of Bridge:: inside the class 2018-11-15 19:19:04 +01:00
logmanoriginal
6f4a8f4d03 [Bridge] Rename to in setWorkingDir 2018-11-15 19:17:18 +01:00
logmanoriginal
39652bb050 [Bridge] Rename to 2018-11-15 19:16:37 +01:00
logmanoriginal
fcac5b8b92 [Bridge] Cleanup documentation and exception messages 2018-11-15 19:15:08 +01:00
logmanoriginal
6f7b56cba8 bridge: Rename setDir and getDir to setWorkingDir and getWorkingDir 2018-11-15 19:07:33 +01:00
logmanoriginal
86ac0a4866 [Cache] Fix typos 2018-11-15 19:00:48 +01:00
logmanoriginal
4a99c6e630 cache: Rename setDir and getDir
- Rename setDir to setWorkingDir
- Rename getDir to getWorkingDir
- Rename parameter $workingDir to $dir in getWorkingDir
2018-11-14 20:39:45 +01:00
logmanoriginal
e8442a3bf8 [Cache] Refactor class
general

- Use self:: instead of Cache:: or static::
- Rename $dirCache to $workingDir
- Initialize $workingDir with null

function setDir

- Clear previous working directory before checking input parameters
- Change wording for the exception messages
- Store realpath instead of raw parameter
- Add path separator
  This ensures the path always ends with the path separator, as assumed
  by Cache::create
- Add check if the provided working directory is a valid directory

function getDir

- Use static parameter instead of function variable
- Change wording for the exception message

function create

- Rename parameter $nameCache to $name
- Rename $pathCache to $filePath
- Change wording for the exception messages

function isValidNameCache

- Rename function to isCacheName
- Rename parameter $nameCache to $name
- Explain in the function documentation the meaning of a 'valid name'
- Ensure Boolean return value (preg_match returns integer)
- Check if $name is a string
- Use slashes to enclose the regex
2018-11-14 20:33:44 +01:00
logmanoriginal
427688fd67 [Cache] Add documentation 2018-11-14 17:06:07 +01:00
logmanoriginal
4a6b3654eb [Bridge] Add and rewrite documentation compatible to phpDocumentor
This is the first step in adding documentation to the core library
of RSS-Bridge. The documentation is not yet extracted by phpdoc,
yet may prove useful to anyone interested in starting with RSS-Bridge.
2018-11-13 20:28:17 +01:00
logmanoriginal
5f867c00b4 [CONTRIBUTING] Add new coding style policies 2018-11-13 18:36:37 +01:00
logmanoriginal
c15b25a07d core: Fix PHPCS violations 2018-11-13 18:27:05 +01:00
logmanoriginal
c296e73c18 [phpcs] Add rules for method declarations in classes 2018-11-13 18:25:37 +01:00
logmanoriginal
007ee4d858 [Bridge] Fix broken bridge initialization
Commit e26d61e introduced a bug that causes the error message "The
bridge you [sic!] looking for does not exist." if the bridge name
specified in the query ends on "Bridge"
(i.e. '&bridge=SoundcloudBridge'), while other queries work fine
(i.e. '&bridge=Soundcloud').

This commit fixes that issue by sanitizing the bridge name before
creating the class.

References #922
2018-11-13 17:36:06 +01:00
Thomas Dalichow
dd95ec6200 core: Fix grammar (#923) 2018-11-13 17:24:36 +01:00
Eugene Molotov
d951000c23 [index] Redirect _cache_timeout requests if the option is disabled (#894)
Requesting `_cache_timeout` on servers where this option is disabled currently results in the error message 'This server doesn\'t support "_cache_timeout"!'. This commit changes that behavior to redirect to the query without `_cache_timeout`.
2018-11-13 17:19:00 +01:00
triatic
51634a72e0 [TwitterBridge] Reorder quoted tweets (#921)
Put content before quoted tweet to match the display order on Twitter
2018-11-12 19:59:46 +01:00
logmanoriginal
78c69b08f0 [index] Fix invalid bridge name FlickrExploreBridge => FlickrBridge 2018-11-10 22:33:19 +01:00
logmanoriginal
3bb3353897 [Bridge] Use static variable in listBridges()
This prevents the function from re-loading the same data over and over
again. Instead the same data is returned on each call, during a single
request.
2018-11-10 22:31:40 +01:00
logmanoriginal
e26d61ec0a core: Refactor bridge whitelisting
- Move all whitelisting functionality inside Bridge.php
- Set default whitelist once in index.php using Bridge::setWhitelist()
- Include bridge sanitizing inside Bridge.php
    Bridge::sanitizeBridgeName($name)

Bridge.php now maintains the whitelist internally.
2018-11-10 22:26:58 +01:00
logmanoriginal
a0490e3673 core: Add Debug::isEnabled() and Debug::isSecure()
Also adds documentation to Debug.php!

* Debug::isEnabled()

Checks if the DEBUG file exists on disk on the first call (stored in
memory for the duration of the instance). Returns true if debug mode
is enabled for the client.

This function also sets the internal flag for Debug::isSecure()!

* Debug::isSecure()

Returns true if debuging is enabled for specific IP addresses, false
otherwise. This is checked on the first call of Debug::isEnabled().
If you call this function before Debug::isEnabled(), the default value
is false.
2018-11-10 20:50:34 +01:00
logmanoriginal
c63af2e7ad core: Add separate Debug class
Replaces 'debugMessage' by specialized debug function 'Debug::log'.
This function takes the same arguments as the previous 'debugMessage'.

A separate Debug class allows for further optimization and separation
of concern.
2018-11-10 20:03:05 +01:00
logmanoriginal
9379854f7a core: Define path to whitelist.txt in rssbridge.php 2018-11-10 19:51:37 +01:00
logmanoriginal
ecdac1b089 core: Add path separator to PATH_CACHE 2018-11-10 19:48:05 +01:00
logmanoriginal
2104fc4d58 core: Move initialization for static paths to rssbridge.php
Bridge, Format and Cache are all part of the core logic of RSS-Bridge
and should therefore be initialized centrally
2018-11-10 19:42:54 +01:00
logmanoriginal
697d63bb96 core: Rename RssBridge.php to rssbridge.php
Using lower case letters because the file doesn't implement a class.
2018-11-10 19:01:57 +01:00
logmanoriginal
2bb13169b4 [Configuration] Use FILTER_VALIDATE_EMAIL on admin/email
This prevents including arbitrary data as email address.
2018-11-10 18:43:16 +01:00
logmanoriginal
4713fb6190 Bump version to dev.2018-11-10 2018-11-10 18:11:49 +01:00
logmanoriginal
a08811f147 Bump version to 2018-11-10 2018-11-10 18:04:58 +01:00
logmanoriginal
a935e310ff travis: Rewrite checks and add PHP compatibility tests
- Remove HHVM

HHVM recently announced ending PHP support:
https://hhvm.com/blog/2018/09/12/end-of-php-support-future-of-hack.html

"HHVM v3.30 will be the last release series where HHVM aims to support
PHP. [...] Ultimately, we recommend that projects either migrate
entirely to the Hack language, or entirely to PHP7 and the PHP runtime."

RSS-Bridge never "officially" supported HHVM, so support can be removed.

- Use composer for all versions

PHP 5.6 is using PEAR, while all other versions use Composer to manage
packages and dependencies. This commit removes PEAR for PHP 5.6 in favor
of Composer. This also simplifies the script.

- Add PHP compatibility tests

Uses https://github.com/PHPCompatibility/PHPCompatibility

RSS-Bridge supports PHP 5.6 or higher. This commit adds tests to check
compatibility and detect breaking changes.

"phpcompatibility.xml" contains the ruleset.

Notice: Technically RSS-Bridge requires PHP 5.6.1, but for some reason
PHPCompatibility doesn't accept "5.6.1" for "testVersion". This is why
INI_SCANNER_TYPED is excluded from tests.

- Rearrange tests

PHP 5.6:
  - Coding style (phpcs.xml)
  - PHP compatibility (phpcompatibility.xml)

PHP 7.0:
  - Coding style (phpcs.xml)
  - Unit tests (phpunit.xml) - using stable release of PHPUnit

PHP nightly:
  - Coding style (phpcs.xml)
  - Unit tests (phpunit.xml) - using latest version of PHPUnit
  - PHP compatibility - no exceptions for PHP 5.6+

- Documentation added to improve maintainability
2018-11-09 20:49:47 +01:00
LogMANOriginal
7e3787a185 .github: Add issue template for bridge requests
This commit adds an issue template for bridge requests, automatically suggested to anyone reporting a new issue.

References https://gist.github.com/4c38d575de8f1edd386fe7c2d529ab6f

Closes #759
2018-11-08 19:45:16 +01:00
logmanoriginal
039c032798 Add folder for GitHub related files
For more information see
https://help.github.com/articles/setting-guidelines-for-repository-contributors/
2018-11-08 19:31:33 +01:00
logmanoriginal
cb91d9cce8 [FacebookBridge] Fix media origin info is not inside a tag
References #912
2018-11-08 19:24:14 +01:00
triatic
bf91f106b4 [FacebookBridge] Remove "Posts" from author name (#917) 2018-11-08 19:04:58 +01:00
logmanoriginal
0b2ede35cd [FacebookBridge] Don't remove origin information from embedded media
References #912
2018-11-08 18:59:12 +01:00
logmanoriginal
5842bdfc83 [FacebookBridge] Simplify implementation 2018-11-08 18:45:25 +01:00
logmanoriginal
68ee24d6bd [FacebookBridge] Remove videos and views
This commit adds filters to remove embedded videos and view counts from
all posts. This doesn't remove the preview image for videos, which are
embedded separately.
2018-11-08 18:36:11 +01:00
logmanoriginal
104ae2298e [FacebookBridge] Remove hidden elements
Hidden elements are used for error conditions and generally made
visible using JavaScript. Since RSS-Bridge doesn't support JS, these
error messages are shown in the final feed. For example:

"It looks like you may be having problems playing this video. If so,
please try restarting your browser."

This commit removes all hidden elements to prevent error messages being
added to the feed.

- "It looks like you may be having problems playing this video. If so,
please try restarting your browser."
2018-11-08 18:24:05 +01:00
logmanoriginal
7026684e34 [FacebookBridge] Don't remove description of embedded media
FB includes origin information (i.e. "YOUTUBE.COM") as well as
descriptions with embedded media (images and video).

These details are currently being removed by the bridge.

This commit changes implementation to only remove origin information
and keep the media description in place. The media description consists
of two elements - title and description. The title provided by FB is
included in an anchor, which gets replaced by a paragraph with the
same contents to improve readability.

References #912
2018-11-08 18:12:57 +01:00
teromene
0b792d77eb [Rue89Bridge] Fix style. 2018-11-07 23:16:28 +01:00
teromene
110b865a54 [Rue89Bridge] Entirely rewrite the bridge. It now uses the JSON api. 2018-11-07 23:13:45 +01:00
teromene
19a7f10160 [InstagramBridge] Support Instagram Locations. Fixes #705. 2018-11-07 22:17:53 +01:00
Antoine Turmel
42e25e7fc0 [OnVaSortirBridge] New Bridge (#914)
Bridge to expand OnVaSortir RSS feed to get the full description of an event
2018-11-07 18:52:29 +01:00
logmanoriginal
4b7fea5ebc [RssBridge] Include interfaces once 2018-11-06 19:23:32 +01:00
logmanoriginal
95bd206e9d core: Move REPOSITORY from index.php to RssBridge.php 2018-11-06 18:53:35 +01:00
logmanoriginal
9910310652 [BridgeImplementationTest] Use PATH_LIB_BRIDGES 2018-11-06 18:46:18 +01:00
logmanoriginal
12f0e5a360 [RssBridge] Include path separator in PATH_* 2018-11-06 18:44:45 +01:00
logmanoriginal
81ba96ff94 core: Add PATH_LIB_BRIDGES, PATH_LIB_FORMATS and PATH_LIB_CACHES
- PATH_LIB_BRIDGES defines the path to bridges
- PATH_LIB_FORMATS defines the path to formats
- PATH_LIB_CACHES defines the path to caches

Include constants in RssBridge.php for consistency
2018-11-06 18:42:27 +01:00
logmanoriginal
984f0b24d0 [RssBridge] Rename PATH_VENDOR to PATH_LIB_VENDOR
This improves clarity for the parameters
2018-11-06 18:39:05 +01:00
logmanoriginal
2126db84ac core: Replace CACHE_DIR by PATH_CACHE
Move CACHE_DIR from index.php to /lib/RssBridge.php and change name
to PATH_CACHE.

PATH_CACHE is one of the core paths of RSS-Bridge and should therefore
be defined in the core file RssBridge.php.
2018-11-06 18:35:43 +01:00
logmanoriginal
4bf45df18e [RssBridge] Simplify documentation for this file
- Remove file documentation and license remark (defined in repository
scope - see README / UNLICENSE)

- Remove example usage (if necessary should be included in the Wiki)
2018-11-06 18:31:48 +01:00
logmanoriginal
a88b148d20 [RssBridge] Add PATH_LIB
Add constant PATH_LIB, pointing to '/lib' to make the include process
same for vendor and lib files.
2018-11-06 18:24:07 +01:00
logmanoriginal
f564925ba0 [RssBridge] Use require_once instead of require
"The require_once statement is identical to require except PHP will
check if the file has already been included, and if so, not include
(require) it again."

-- http://php.net/manual/en/function.require-once.php
2018-11-06 18:15:10 +01:00
logmanoriginal
22e8f8b4aa [RssBridge] Skip searching vendor files
Vendor files (simple_html_dom.php and urljoin.php) are included in the
repository and therefore shipped with all releases. If one of the files
is missing, either the repository or the release is incomplete.

PHP will generate error messages if either of the files is missing, so
there is no need to check availability manually unless it is done for
all files (which doesn't make sense because they are part of the
repository).
2018-11-06 18:11:18 +01:00
logmanoriginal
bfae04d1fe [RssBridge] Include __DIR__ in PATH_VENDOR 2018-11-06 18:08:53 +01:00
teromene
723bd1150a Remove tracking codes from Facebook posts 2018-11-06 16:58:58 +01:00
Thibault Couraud
53d2fbe3a5 [FindACrewBridge] Implement bridge for findacrew.net (#901)
* [FindACrewBridge] Implement bridge for findacrew.net - sailing boats offers
2018-11-06 14:57:54 +01:00
Thibault Couraud
3babd02658 [CrewbayBridge] Implement bridge for crewbay.com (#902)
* [CrewbayBridge] Implement bridge for crewbay.com - sailing boats offers
2018-11-06 14:56:23 +01:00
logmanoriginal
3031fa406d core: Set code in header() instead of calling http_response_code() 2018-11-05 19:29:01 +01:00
logmanoriginal
85c34a0960 [CHANGELOG.md] Remove file
The latest changelog is available at
https://github.com/RSS-Bridge/rss-bridge/releases
2018-11-05 19:14:44 +01:00
logmanoriginal
5deb86acff core: Replace PHP_VERSION_REQUIRED by static text
The required PHP version is used in one place only and
therefore shouldn't require a constant
2018-11-05 19:07:33 +01:00
logmanoriginal
946e66e9df core: Use REPOSITORY constant where applicable 2018-11-05 19:05:59 +01:00
logmanoriginal
1a00dfa412 [index.php] Change user agent to constant and include current version 2018-11-05 19:04:30 +01:00
Corentin Garcia
0f8443e1d3 [RainbowSixSiegeBridge] Fix missing news (#908) 2018-11-05 18:20:17 +01:00
Albirew
7d474e5361 [ThePirateBayBridge] Fix TLD from .org to .wf (#907) 2018-11-05 18:17:46 +01:00
Corentin Garcia
8c97953211 [CommonDreamBridge] Promote to secure bridge (fix #777) (#909) 2018-11-05 17:32:11 +01:00
logmanoriginal
d987ceec73 [CONTRIBUTING.md] Include all policies and link to the Wiki 2018-11-05 14:07:14 +01:00
logmanoriginal
392e3ff6c7 phpcs: Fix violations 2018-11-05 12:55:58 +01:00
logmanoriginal
e295dc5a79 [phpcs] Add check for concatenation operator spacing
The concatenation operator should have one space before and after
2018-11-05 12:52:18 +01:00
logmanoriginal
b9f6bc8197 [XenForoBridge] Fix broken conditions
Restore functionality for https://xenforo.com/community/
2018-11-05 12:19:45 +01:00
logmanoriginal
9c1c0f2974 [XenForoBridge] Fix broken checks 2018-11-05 12:05:14 +01:00
logmanoriginal
65da157fff [XenForoBridge] Add new bridge
Adds a bridge for forums powered by XenForo (see https://xenforo.com).

Support between forums may vary due to ever changing versions with no
clear distinction. Especially timestamps may not work depending on the
supported language (should currently work on en-US and de-DE).

Tested on

- https://xenforo.com/community/
- http://www.ign.com/boards/

Notice: XenForo provides RSS feeds for forums (but not specific topics).
For example: https://xenforo.com/community/forums/-/index.rss
2018-11-05 12:00:12 +01:00
triatic
5fe943562a [FB2Bridge] Prevent shared post duplication (#904)
Prevent shared posts appearing twice in feed.
2018-11-05 11:46:56 +01:00
Thibault Couraud
c58331f74d [BAEBridge] Add bridge for bourse-aux-equipiers.com (#903) 2018-11-05 11:38:22 +01:00
Antoine Turmel
145a46ae1d [ThingiverseBridge] Add new bridge (#869) 2018-11-05 11:27:32 +01:00
mr-flibble
1a7a7bad98 [contents.php] Fix typo (#900)
This fixes "The requested resouce cannot be found!" on line 67
2018-11-05 11:10:32 +01:00
Yardena Cohen
27d6a22675 core: Display optional administrator email (#896) 2018-11-05 10:46:44 +01:00
teromene
b55ec51e0e Fix timestamp decoding 2018-11-04 21:50:18 +01:00
hunhejj
07b4c72d5d [InstagramBridge] Don't add duplicated urls when parsing Instagram stories (#715) 2018-11-03 12:12:37 +01:00
logmanoriginal
2e6cbd1ce7 [GitHubGistBridge] Fix broken bridge
`defaultLinkTo` makes anchors point to the correct path which broke
parsing because it expected href to start with `#gistcomment`.

This commit changes the implementation to make `defaultLinkTo` point
to the correct page (using `getURI` instead of `self::URI`) and search
with `*=` instead of `^=`.
2018-11-03 11:56:51 +01:00
LogMANOriginal
2ac2f3dc66 [README] Add info about feed readers
References #892
2018-11-02 11:45:45 +01:00
logmanoriginal
e2dfea2b77 [index.php] Filter parameter '_error_time' from queries
The parameter is used in error feeds. Since RSS-Bridge returns valid
feeds for error conditions, feed readers may attempt to access the
URI returned for the feed item in order to collect additional data,
thus including the parameter '_error_time' in the query.

This results in another error message, because it is an invalid input
parameter. Filtering the parameter allows RSS-Bridge to return the
original feed.

References #882
2018-11-02 11:05:48 +01:00
Yardena Cohen
c4896c7791 [Configuration] Fix open_basedir warnings (#887)
If .git/HEAD isn't in open_basedir it'd throw ugly warnings.
Suppress errors while checking if file is readable
2018-10-27 10:53:45 +02:00
logmanoriginal
7621784598 bridges: Add favicon to bridges missing it
Adds favicon to bridges that support it. Some sites prevent downloading
favicons, those bridges are left untouched.

Affected bridges:

- AutoJMBridge
- BandcampBridge
- BlaguesDeMerdeBridge
- BloombergBridge
- BundesbankBridge
- ChristianDailyReporterBridge
- ContainerLinuxReleasesBridge
- DailymotionBridge
- DiceBridge
- DribbbleBridge
- EliteDangerousGalnetBridge
- ElsevierBridge
- FacebookBridge
- FB2Bridge
- FDroidBridge
- FierPandaBridge
- GooglePlusPostBridge
- JapanExpoBridge
- KATBridge
- KernelBugTrackerBridge
- LegifranceJOBridge
- NotAlwaysBridge
- NyaaTorrentsBridge
- PinterestBridge
- RadioMelodieBridge
- RainbowSixSiegeBridge
- SupInfoBridge
- TagBoardBridge
- TebeoBridge
- TheTVDBBridge
- WhydBridge
- ZoneTelechargementBridge
2018-10-26 19:10:58 +02:00
logmanoriginal
1cfe939927 [AskfmBridge] Fix broken bridge
References #774
2018-10-24 18:33:07 +02:00
logmanoriginal
c56f7abc2a [FacebookBridge] Reduce occurrence of HTTP error 302
Facebook returns "HTTP/1.1 302 Found" when requesting:
  https://www.facebook.com//pg/username/posts?_fb_noscript=1
Automatically redirecting to:
  https://www.facebook.com/username/posts/

We receive a positive response faster when directly requesting the
correct page:
  https://www.facebook.com/username/posts?_fb_noscript=1

Notice: This is just a minor adjustment to improve performance while
requesting data from the server. The previous version worked fine as
well.
2018-10-24 17:27:46 +02:00
logmanoriginal
e3030cbbfd [InstagramBridge] Reduce occurrence of HTTP error 301
Instagram returns "HTTP/1.1 301 Moved Permanently" on each request
to "https://instagram.com/" because the correct location is
"https://www.instagram.com/".

Instagram will respond with "HTTP/1.1 301 Moved Permanently" if the
URI for the requested user doesn't end with a slash.

Notice: This is only a minor enhancement to prevent error 301 from
happening. The previous version worked fine as is.
2018-10-24 16:42:28 +02:00
logmanoriginal
953c6e1022 [contents] Skip setting options on empty array 2018-10-24 16:28:26 +02:00
logmanoriginal
dbd44f64dd [contents] Add debug messages for 'getContents'
Adds additional messages to the error log when fetching contents. The
data is helpful in finding issues with receiving contents from servers.

References: #879, #882, #884
2018-10-24 16:10:33 +02:00
logmanoriginal
89ca42da54 [index] Always write exceptions to error.log
Exceptions are reported to users, but they do not necessarily appear
in the error log on the server. Using 'error_log' we can explicitly
write exceptions and error messages to the log file, using the
standard PHP message format.

For more information see https://stackoverflow.com/a/26867035
2018-10-24 15:58:12 +02:00
sysadminstory
b4b5340b7e [ZoneTelechargementBridge] Make the bridge more robust to URL change (#881)
Using the classical www.zone-telechargement1.org as base URL, the bridge will
always be redirected to the actual wwX.zone-telechargement1.org final URL. This
makes the bridge more robust to URL changes.
2018-10-22 19:22:02 +02:00
Eugene Molotov
a508dddb36 [core] Fixed broken caching (#880) 2018-10-22 19:14:49 +02:00
logmanoriginal
cb488d9d8c [FacebookBridge] Fix broken feeds
This commit collects the original contents from a different
tag to prevent this issue. The root cause is unknown but closely
related to the regex.

References #877
2018-10-20 15:45:20 +02:00
Antoine Turmel
9820ad5c0f [BridgeCard] Fix checkbox default value (#874)
The current solution just output "1" when checked instead of "checked"
2018-10-20 13:14:46 +02:00
Antoine Turmel
ea2d54523d [EtsyBridge] Fix bridge and correct typos (#873) 2018-10-20 13:08:03 +02:00
Eugene Molotov
87d218296e [YoutubeBridge] Fix playlist mode (#876)
* Corrected duration text selector
* Request YouTube page with English localization
* Filter video items in the beginning of the loop
2018-10-20 12:43:48 +02:00
teromene
afd5ef0f1d [FB2Bridge] Add images support
[FB2Bridge] Add basic "cards" support
2018-10-18 21:10:02 +02:00
teromene
30bc5179c2 Fix number of fetched items.
Strip the username.
2018-10-18 18:44:11 +02:00
teromene
7596be65f2 Use a new URL for the cursor. Should fix #851.
Remove the "...More" item in the output
Remove the information card data
2018-10-18 18:07:07 +02:00
Eugene Molotov
16f0ee7104 [InstagramBridge] added caption existance check in getInstagramStory (#865)
* [InstagramBridge] added caption existance check in getInstagramStory

* [InstagramBridge] Coding policy fixes
2018-10-18 16:45:03 +02:00
fluffy
e0323f06cd update php-urljoin (#867) 2018-10-18 16:43:39 +02:00
logmanoriginal
717b0bdd9c Fix items link to localhost
References #864
2018-10-16 19:16:51 +02:00
logmanoriginal
62d737efe2 Replace emoticon images by their textual representation
References #850
2018-10-16 19:02:55 +02:00
triatic
6fce03daa7 [FB2Bridge] Add updated timestamps to each post (#849)
Additionally, exclude shared posts from output since they already exist inside other posts.
2018-10-16 18:34:39 +02:00
logmanoriginal
7561c0685d [FacebookBridge] Fix 'SpSonSsoSredS' text in title
The function 'defaultLinkTo' applied to the source HTML does break
regex matches later in the bridge. We need to apply the function
right before adding the contents to the item for the bridge to work
properly.

References #856
2018-10-15 19:53:46 +02:00
logmanoriginal
f48eac854f Bump version to 'dev.2018-10-15' 2018-10-15 18:59:03 +02:00
logmanoriginal
a87e7781b1 Bump version to 2018-10-15 2018-10-15 18:54:53 +02:00
logmanoriginal
0dc761d6cf [README] Update authors
Not sure why, but the GitHub API responded with false results the
last time. Cleaning up to reflect current list of contributors.
2018-10-15 18:53:27 +02:00
logmanoriginal
d14f8e3c83 [BundesbankBridge] Add new bridge 2018-10-15 18:38:42 +02:00
logmanoriginal
b4aea21f71 [DesoutterBridge] Add new bridge 2018-10-15 18:35:49 +02:00
logmanoriginal
c06a09fe99 [GlassdoorBridge] Add new bridge 2018-10-15 18:33:02 +02:00
sysadminstory
704ad50607 [DealabsBridge] Follow website changes (#852)
Pepper changed the CSS class of some elements. The bridge was changed to
follow these changes.
2018-10-15 18:25:04 +02:00
sysadminstory
d89c65d219 [ZoneTelechargementBridge] Update the base URL and make URI unique (#853)
- Base URL updated
- Show name has different styles on the Website, use another way to get the show name
- Entry URIs are now unique to make sure RSS readers don't treat episodes as duplicates
- No more new lines in the feed or item title
2018-10-15 18:23:08 +02:00
sysadminstory
9a3c776096 [ExtremeDownloadBridge] Make URI and titles unique (#854)
- Entry URIs are unique to make sure RSS readers don't treat episodes as duplicates
- Titles are unique to make sure RSS readers don't treat streams and downloads as duplicates
2018-10-15 18:19:57 +02:00
triatic
85e8a67568 [MrssFormat.php] Prevent PHP Notice (#858)
Prevent PHP Notice when running in CLI mode
2018-10-15 18:14:06 +02:00
Nicolas Delsaux
ee158468fa Expanded Sexactu to cover the whole GQ magazine (#861)
The bridge has been expanded to better cover the whole GQ magazine.
It should support all countries (provided they all use the same absurdly shitty publication system).
It is guaranteed to be only tested with sexactu articles (that I now obtain by loading Maïa Mazaurette author page).
2018-10-15 18:09:20 +02:00
logmanoriginal
5779f641c0 [FacebookBridge] Add option to limit number of returned items
This commit adds a new optional parameter 'limit' which can be used
to limit the number of items returned by this bridge (i.e. '&limit=10')

As requested in #669
2018-10-15 17:35:10 +02:00
LogMANOriginal
b90bcee1fc Return exceptions in requested feed formats (#841)
* [Exceptions] Don't return header for bridge exceptions
* [Exceptions] Add link to list in exception message

This is an alternative when the button is not rendered
for some reason.

* [index] Don't return bridge exception for formats
* [index] Return feed item for bridge exceptions
* [BridgeAbstract] Rename 'getCacheTime' to 'getModifiedTime'
* [BridgeAbstract] Move caching to index.php to separate concerns

index.php needs more control over caching behavior in order to cache
exceptions. This cannot be done in a bridge, as the bridge might be
broken, thus preventing caching from working.

This also (and more importantly) separates concerns. The bridge should
not even care if caching is involved or not. Its purpose is to collect
and provide data.

Response times should be faster, as more complex bridge functions like
'setDatas' (evaluates all input parameters to predict the current
context) and 'collectData' (collects data from sites) can be skipped
entirely.

Notice: In its current form, index.php takes care of caching. This
could, however, be moved into a separate class (i.e. CacheAbstract)
in order to make implementation details cache specific.

* [index] Add '_error_time' parameter to $item['uri']

This ensures that error messages are recognized by feed readers as
new errors after 24 hours. During that time the same item is returned
no matter how often the cache is cleared.

References https://github.com/RSS-Bridge/rss-bridge/issues/814#issuecomment-420876162

* [index] Include '_error_time' in the title for errors

This prevents feed readers from "updating" feeds based on the title

* [index] Handle "HTTP_IF_MODIFIED_SINCE" client requests

Implementation is based on `BridgeAbstract::dieIfNotModified()`,
introduced in 422c125d8e and
simplified based on https://stackoverflow.com/a/10847262

Basically, before returning cached data we check if the client send
the "HTTP_IF_MODIFIED_SINCE" header. If the modification time is
more recent or equal to the cache time, we reply with "HTTP/1.1 304
 Not Modified" (same as before). Otherwise send the cached data.

* [index] Don't encode exception message with `htmlspecialchars`
* [Exceptions] Include error message in exception
* [index] Show different error message for error code 0
2018-10-15 17:21:43 +02:00
logmanoriginal
996295e82f Add 'dev.' to the release version in master
This helps (roughly) identifying versions when opening issues on
GitHub, using the latest ZIP file for master.

References #773
2018-09-26 20:04:27 +02:00
logmanoriginal
13bd7fe21b [contents] Return error if the server responded with any code other than 200 2018-09-26 19:16:02 +02:00
logmanoriginal
fcc9f9fd61 [FacebookBridge] Use alternative URI to load more posts
The URI "https://facebook.com/username?_fb_noscript=1" returns two
posts per user. Some profiles, however, are very active, causing the
bridge to miss items if more than two posts are send within the cache
duration (5 minutes).

The alternative suggested in #669 is to use a different URI:
"https://facebook.com/pg/username/posts?_fb_noscript=1"

While the contents of this URI essentially look the same when viewed
in a browser, it actually returns more than 10 posts depending on the
profile.

References #669
2018-09-26 18:24:46 +02:00
logmanoriginal
e1c4914b1c [FacebookBridge] Optimize for readability 2018-09-25 18:56:33 +02:00
logmanoriginal
93e7ea9fea [HtmlFormat] Make feeds available via syndication links 2018-09-22 19:51:18 +02:00
logmanoriginal
2d1b446bd1 [DevToBridge] Add new bridge
Returns feeds for tags from https://dev.to

References #840
2018-09-22 18:57:07 +02:00
logmanoriginal
1d451610d6 [ParameterValidator] Move 'getQueriedContext' from BridgeAbstract 2018-09-22 17:04:55 +02:00
logmanoriginal
f853ffc07c [ParameterValidator] Refactor 'validation' into 'ParameterValidator'
Adds a new class 'ParameterValidator' to replace the functions from
'validator.php', separating private functions from 'validateData' to
class private functions in the process.

Instead of echoing error messages, adds messages to a private variable,
accessible via 'getInvalidParameters'.

BridgeAbstract now adds invalid parameter names to the error message.
2018-09-22 16:42:04 +02:00
logmanoriginal
e3a5a6a170 [index] Update and improve parameter handling for bridge and cache
- Use 'array_diff_key' instead of 'unset'
- Remove parameters for caches

By removing certain parameters for caches, the loading times can be
improved considerably:

* action: It doesn't matter which action the user took to generate
feed items.

* format: This has the biggest impact on performance, because cached
items are now shared between different formats (i.e. try switching
between Atom, Html and Mrss and compare previous vs. now). If a
server handles lots of requests, this may even reduce bandwidth if
the same contents are requested for different formats.

* _noproxy: The proxy behavior has no impact on the produced items,
so it can be ignored.

* _cache_timeout: This is another option which might impact performance
for some servers, especially if 'custom_timeout' has been enabled in
the configuration. Requests with different cache timeouts no longer
result in separate cache files.
2018-09-22 15:44:03 +02:00
logmanoriginal
243e324efc [NineGagBridge] Fix missing sections breaking feeds
Posts may supply a list of 'sections' or a single 'postSection'

References #844
2018-09-22 15:19:14 +02:00
logmanoriginal
ae58b1566e [NineGagBridge] Remove type hinting
Type hinting for strings doesn't work prior to PHP 7, see
http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration

References #837
2018-09-22 15:19:14 +02:00
sysadminstory
c044694b21 [ZoneTelechargementBridge] Sort episodes from newest to oldest (#835)
References #834
2018-09-21 20:22:49 +02:00
triatic
db24f55c86 [FB2Bridge] Do not strip <h3> and <h4> (#836)
Do not strip <h3> and <h4>. Output looks better when they are retained. See attached.
2018-09-21 20:19:22 +02:00
logmanoriginal
eb30038d6b [README] Update and reorganize 2018-09-16 18:20:35 +02:00
logmanoriginal
712a581ed6 [README] Add badge for Guix release
Unfortunately there is no way to query the current package version,
so this is only a placeholder
2018-09-16 16:01:51 +02:00
logmanoriginal
d3df4b51b8 [README] Add badge for current debian release 2018-09-16 15:13:30 +02:00
logmanoriginal
e6476a600d [KununuBridge] Fix broken bridge and simplify implementation 2018-09-16 09:55:35 +02:00
Grégory T
811e8d8c88 [ETTVBridge] Improvements and bug fixes (#682)
* Fix typo with status field
* Comply with other bridges

Change the uri element of an item to point, not on the magnet link, but on the page, as similar bridges do.

* Improved to return name & uri matching with query

This change makes it possible for the feed reader to discover a title and url consistent with the user's search.
2018-09-15 17:11:36 +02:00
logmanoriginal
adc6f72e97 [style] Fix first letter of labels not capitalized
This error is caused by setting label::before { content: " "; },
which makes the first letter a whitespace on all labels, neccessary
 for browsers that doesn't support the grid layout.

This commit clears the content if the browser supports the grid layout,
properly capitalizing labels again. If a browser doesn't support grid
layout, labels stay as they are provided by the bridge.
2018-09-15 17:04:20 +02:00
logmanoriginal
182153485c [Arte7Bridge] Move parameter examples into tool tip for readability 2018-09-15 16:50:10 +02:00
LogMANOriginal
bf9946d1fc CSS adjustments to improve readability for bridge parameters (#763)
* Group common selectors
* Fix indentation using tabs
* Use same styles for number and text inputs
* Use grid layout for parameters

Introduces the grid layout for bridge parameters. All parameters are
arranged in a grid to improve readability. Read more on grid layouts
at

- https://www.w3schools.com/css/css_grid.asp
- https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout

Notice:

Grid layouts are not supported in very old browser versions:
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/CSS_Grid_and_Progressive_Enhancement

This is why @supports checks for browser support (not supported in IE)
https://developer.mozilla.org/en-US/docs/Web/CSS/@supports#Browser_compatibility

In case grid layout is not supported, the displayed form is usable
but not very pretty due to <br> being removed by this commit for
cosmetic reasons (breaks grid layout).

Unfortunately it doesn't seem possible to insert line breaks manually
via '::after { content: '\A' }' in cases where grid layout isn't
supported.

* Add padding to card parameters

Adds padding to parameters to improve readability. For bridges without
parameters (count($parameters) === 0), the parameter 'div' is no longer
created.

* Add colon ':' after label via CSS
* Capitalize first letter of label for readability
* Fix checkbox isn't aligned left

Sets the size of the checkbox to 20x20 px for good measure.

* Harmonize formatting
* Add new style to number and select boxes

References #797

* Add fallback solution for browsers without grid support
2018-09-15 16:39:50 +02:00
triatic
ec60752650 [FB2Bridge] Prevent Facebook link href's ending in two quotes (#831)
Additionally prevent Facebook links having two forward slashes after the hostname.
2018-09-15 15:16:15 +02:00
sysadminstory
6688cf0c3b [AutoJMBridge] Fix concatenation bug (#833) 2018-09-15 15:12:34 +02:00
ORelio
ae45a8cfee [contents] Fix open_basedir warning (#832)
References #818
2018-09-15 14:46:11 +02:00
Matthew Seal
e34ef6cb4f [MrssFormat] Escape double quotes in XML attributes (#813)
XML attributes need to have certain characters escaped to be valid. The title attribute can have double quotes in it which need to be properly encoded for attributes.
2018-09-15 14:13:05 +02:00
sysadminstory
5c92a736fa [ZoneTelechargementBridge] Added Bridge for ww2.zone-telechargement1.org (#829)
* [ZoneTelechargementBridge] Added Bridge for ww2.zone-telechargement1.org

Goal for this bridge is to follow the episode publication of a TV show
season while it's broadcasted on the TV.
2018-09-13 19:36:48 +01:00
Eugene Molotov
911bcfb246 [PikabuBridge] Implemented bridge (#830)
* [PikabuBridge] Implemented bridge
2018-09-13 12:52:26 +01:00
ZeNairolf
efa550ef61 Add 9gag.com bridge (#801)
* Add 9gag.com bridge
2018-09-13 10:11:42 +01:00
sysadminstory
d5d7683ed3 [AutoJMBridge] New Bridge (#827)
* [AutoJMBridge] New Bridge

This bridge will show all the car offers AutoJM has for the model you
choosed and using your filter. Very useful to wait for a cheap price for
a new car !
2018-09-13 10:05:07 +01:00
triatic
fe94914eb5 [AtomFormat.php] Eliminate PHP Notice when running in CLI mode (#824) 2018-09-12 14:37:27 +01:00
Quentin Delmas
622802e5d4 Fix multiple warnings.
Fix JSON request string in case of empty location
2018-09-12 13:31:11 +01:00
sysadminstory
6da8daf1a3 [DealabsBridge] Fix for #782 and all categories are now available (#821)
This commit fixes #782 by updating the parameter value of 'Maison &
Jardin', but this means the user has to update his RSS Feed URL (.because
of the bridge structure, it would be a nightmare to fix it in another
way)

This commits add all the categories available on Dealabs Website.
2018-09-11 22:11:00 +01:00
la Bécasse
654e502e84 Arte7 collection support (#819)
* Arte7 collection support
2018-09-11 22:09:47 +01:00
sysadminstory
c8ace9e3bd [ExtremeDownloadBridge] Added Bridge for ww1.extreme-d0wn.com (#820)
* [ExtremeDownloadBridge] Added Bridge for ww1.extreme-d0wn.com

Goal for this bridge is to follow the episode publication of a TV show season
while it's broadcasted on the TV.
2018-09-11 20:10:46 +01:00
Monsieur Poutounours
5722a6c139 Adding a bridge for theyetee.com (#809)
* Adding a bridge for theyetee.com

The bridge fetches daily shirts at theyetee.com.
The Yetee offers two new shirts each day, but you can buy them only for a few hours !
Unfortunately, the site don't provide RSS feed, so the only way to keep up to date on new shirt is their daily mailing ... until now !
2018-09-10 20:56:55 +01:00
Quentin Delmas
458b826871 Remove declaration of extractFromDelimiters, it is now a reusable function. Fixes #815 2018-09-10 09:29:19 +01:00
Quentin Delmas
b397a42876 version: Bump to 2018-09-09 2018-09-09 21:00:10 +01:00
Corentin Garcia
111c45d010 [GithubSearchBridge] Fix content parsing, add tags if present (#803)
* [GithubSearchBridge] Fix content parsing, add tags if present

* [GithubSearchBridge] Add categories (from tags)
2018-09-09 20:30:29 +01:00
Corentin Garcia
55b36b0455 [DauphineLibereBridge] Use https, fix content parsing (fix issue #780) (#811) 2018-09-09 20:23:59 +01:00
ORelio
de8cee6a1c Catching up | [Main] Debug mode, parse utils, MIME | [Bridges] Add/Improve 20 bridges (#802)
* Debug mode improvements

 - Improve debug warning message
 - Restore error reporting in debug mode
 - Fix 'notice' messages for unset fields

* Add parsing utility functions

html.php
 - extractFromDelimiters
 - stripWithDelimiters
 - stripRecursiveHTMLSection
 - markdownToHtml (partial)

bridges
 - remove now-duplicate functions
 - call functions from html.php instead

* [Anidex] New bridge

Anime torrent tracker

* [Anime-Ultime] Restore thumbnail

* [CNET] Recreate bridge

Full rewrite as the previous one was broken

* [Dilbert] Minor URI fix

Use new self::URI property

* [EstCeQuonMetEnProd] Fix content extraction

Bridge was broken

* [Facebook] Fix "SpSonsSoriSsés" label

... which was taking space in item title

* [Futura-Sciences] Use HTTPS, More cleanup

Use HTTPS as FS now offer HTTPS
Clean additional useless HTML elements

* [GBATemp] Multiple fixes

- Fix categories: missing "break" statements
- Restore thumbnail as enclosure
- Fix date extraction
- Fix user blog post extraction
- Use getSimpleHTMLDOMCached

* [JapanExpo] Fix bridge, HTTPS, thumbnails

- Fix getSimpleHTMLDOMCached call
- Upgrade to HTTPS as JE now offers HTTPS
- Restore thumbnails as enclosures

* [LeMondeInformatique] Fix bridge, HTTPS

- Upgrade to HTTPS as LMI now offers HTTPS
- Restore thumbnails using small images
- Fix content extraction
- Fix text encoding issue

* [Nextgov] Fix content extraction

- Restore thumbnail and use small image
- Field extraction fixes

* [NextInpact] Add categories and filtering by type

- Offer all RSS feeds
- Allow filtering by article type
- Implement extraction for brief articles
- Remove article limit, many brief articles are publied all at once

* [NyaaTorrents] New bridge

Anime torrent tracker

* [Releases3DS] Cache content, restore thumbnail

- Use getSimpleHTMLDOMCached
- Restore thumbnail as enclosure

* [TheHackerNews] Fix bridge

 - Fix content extraction including article body
 - Restore thumbnail as enclosure

* [WeLiveSecurity] HTTPS, Fix content extraction

- Upgrade to HTTPS as WLS now offers HTTPS
- Fix content extraction including article body

* [WordPress] Reduce timeout, more content selectors

- Reduce timeout to use default one (1h)
- Add new content selector (articleBody)
- Find thumbnail and set as enclosure
- Fix <script> cleanup

* [YGGTorrent] Increase limit, use cache

- Increase item limit as uploads are very frequent
- Use getSimpleHTMLDOMCached

* [ZDNet] Rewrite with FeedExpander

- Upgrade to HTTPS as ZD now offers HTTPS
- Use FeedExpander for secondary fields
- Fix content extraction for article body

* [Main] Handle MIME type for enclosures

Many feed readers will ignore enclosures (e.g. thumbnails) with no MIME type. This commit adds automatic MIME type detection based on file extension (which may be inaccurate but is the only way without fetching the content).

One can force enclosure type using #.ext anchor (hacky, needs improving)

* [FeedExpander] Improve field extraction

- Add support for passing enclosures
- Improve author and uri extraction
- Fix 'notice' PHP error messages

* [Pull] Coding style fixes for #802

* [Pull] Implementing changes for #802

 - Fix coding style issues with str append
 - Remove useless CACHE_TIMEOUT
 - Use count() instead of $limit
 - Use defaultLinkTo() + handle strings
 - Use http_build_query()
 - Fix missing </em>
 - Remove error_reporting(0)
 - warning CSS (@LogMANOriginal)
 - Fix typo in FeedExpander comment

* [Main] More documentation for markdownToHtml

See #802 for more details
2018-09-09 20:20:13 +01:00
Quentin Delmas
123fce4394 [ForGifsBridge] Fix permissions of ForGifsBridge 2018-09-09 17:34:36 +01:00
Quentin Delmas
a3f99c9c3f [GOGBridge] Added bridge for GOG.com 2018-09-09 17:32:36 +01:00
Eugene Molotov
bf30ad127c [FacebookBridge] Removes query string from post links
* [FacebookBridge] Removes query string from post links
2018-09-09 16:31:15 +01:00
logmanoriginal
37f84196b7 [GooglePlusPostBridge] Fix title is empty if content is too short
The bridge would generate empty titles if the content is longer than
50 characters, but doesn't have further spaces in it. With this commit
the title is correctly generated based on the contents, taking missing
spaces into account.

References #786
2018-09-08 17:07:57 +02:00
Corentin Garcia
44764f7182 [GrandComicsDatabaseBridge] Fix links in content (#804) 2018-09-08 11:12:27 +01:00
Antoine Cadoret
19f294d71d Add fields to leboncoin bridge (#783)
* [LeBonCoinBridge] Add fields to LeBonCoinBridge
2018-08-31 14:34:41 +01:00
Teromene
b0e33e4e01 Update LeBonCoinBridge to use the site's API (#795)
* Update LeBonCoinBridge to use the site's API
2018-08-28 14:20:02 +01:00
Eugene Molotov
558fa50a2a [core] Enabled debug mode before including core files (#790) 2018-08-25 20:02:47 +01:00
Eugene Molotov
ffb8b82c73 [FileCache] reseting cached file stat result to have correct getTime() result (#792)
* [FileCache] reseting cached file stat result to have correct getTime() result
2018-08-25 20:00:51 +01:00
Eugene Molotov
422c125d8e [core] Returning 304 http code when returning cached data (#793) 2018-08-25 20:00:38 +01:00
Quentin Delmas
059656c370 Fix phpcs. 2018-08-22 16:25:08 +01:00
Quentin Delmas
9fc1e97efe Avoid bot exclusion. 2018-08-22 16:21:39 +01:00
Quentin Delmas
be3620acb7 Add extension check for the "json" extension. 2018-08-22 16:21:20 +01:00
LogMANOriginal
16c0a61232 [README] Add a "Deploy to Cloud" button for Docker
Adds a button to deploy RSS-Bridge to the Docker Cloud as described here: https://docs.docker.com/docker-cloud/apps/deploy-to-cloud-btn/
2018-08-21 18:40:39 +02:00
Walter Barrett
704a87ad97 Icons: Allow Bridge-specified icons (#788) 2018-08-21 17:46:47 +02:00
sysadminstory
c4cccfe0f3 [LesJoiesDuCode] Switch to HTTPS and remove author (#787)
Website offers now HTTPS, therefore the bridge was switched to it.
The post author is not displayed anymore on the homepage, so it has been
removed.
2018-08-21 17:41:56 +02:00
Marcin C
d07deb0930 css: Modern look for RSS-Bridge (#781) 2018-08-21 17:22:46 +02:00
Piranhaplant
e7dab5d351 Fixed timestamp on Pixiv bridge (#785) 2018-08-18 16:54:24 -03:00
logmanoriginal
ad82d50bbd [CNETBridge] Remove bridge
CNET now provides public feeds at https://www.cnet.com/rss/

References #775
2018-08-12 11:02:44 +02:00
logmanoriginal
c305c1ded7 [BlaguesDeMerdeBridge] Adjust to layout changes
References #767
2018-08-10 21:08:47 +02:00
logmanoriginal
f14a5bd771 [CADBridge] Remove bridge
https://cad-comic.com/ now provides feeds at

- https://cad-comic.com/feed (rss)
- https://cad-comic.com/feed/atom (atom)

Thus multiple alternatives are available to choose from, making this
bridge obsolete:

- FilterBridge (using one of the feeds above)
- WordPressBridge (on the main site)
- One of the two available feeds

References #752
2018-08-10 19:53:32 +02:00
logmanoriginal
a20d5f9af0 tests: reuse RssBridge.php instead of implementing a custom solution 2018-08-10 15:33:32 +02:00
logmanoriginal
ee28b124e0 [DanbooruBridge] Fix bridge
This commit fixes an issue caused by self closing tags not supported
by simplehtmldom (<source>).

Adds a monkey patch to extend simplehtmldom with the ability to detect
that particular tag. Most of the code added is copied directly from
simplehtmldom (see vendor/simplehtmldom) with adjustments to account
for RSS-Bridge formatting.

Related to: https://sourceforge.net/p/simplehtmldom/bugs/83/

Notice: The tag itself is valid according to Mozilla:

The HTML <picture> element serves as a container for zero or more
<source> elements and one <img> element to provide versions of an
image for different display device scenarios. The browser will
consider each of the child <source> elements and select one
corresponding to the best match found; if no matches are found
among the <source> elements, the file specified by the <img>
element's src attribute is selected. The selected image is then
presented in the space occupied by the <img> element.

-- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture

References #753
2018-08-09 21:55:43 +02:00
LogMANOriginal
7dee3a175a [index] Add '?action=list' to list bridges (#493)
Adds a new action '?action=list' to return a list of bridges as JSON formatted text. Each bridge brings following information:

- status (active/inactive)
- uri
- name
- parameters
- maintainer
- description

For inactive bridges only the status is returned.
Bridges that cannot be instantiated are considered inactive.
2018-08-09 19:14:10 +02:00
logmanoriginal
5fea9fc1f5 bridges: Fix bridges failing unit test 2018-08-09 17:04:16 +02:00
logmanoriginal
6bceb2b2db [tests] Add unit test for bridge implementation
Adds unit test for bridge implementations:

- Custom functions must be in protected or private scope
- getName() must return a valid string (non-empty)
- getURI() must return a valid URI
- Each bridge must define constants for NAME, URI, DESCRIPTION and
  MAINTAINER. CACHE_TIMEOUT and PARAMETERS are optional.

The unit test is written for PHPUnit 6.x and will automatically be
tested by Travis-CI for PHP 7.0 (see .travis.yml).

Remarks:

Unit tests for bridge data were scrapped in #378 for complexity
reasons (tests would have to be maintained for each bridge). This
unit test, however, is written for testing all bridges without
taking specific implementation details into account.
2018-08-09 17:04:16 +02:00
Eugene Molotov
df81fa62d1 [VkBridge] Video attachment fixes (#766)
* use defaultLinkTo
* remove duplicate video links
* remove line ending before "Reposted" label
* return newline before reposted string
* remove comments
* use video links that won't require login
* set title if video has no title
2018-08-09 17:02:36 +02:00
Eugene Molotov
f8c6400373 [HtmlFormat] Hide "Categories" label, if array of categories is empty (#765) 2018-08-09 16:46:53 +02:00
176 changed files with 13465 additions and 3096 deletions

View File

@@ -4,5 +4,4 @@ DEBUG
Dockerfile
whitelist.txt
phpcs.xml
CHANGELOG.md
CONTRIBUTING.md

14
.gitattributes vendored
View File

@@ -20,3 +20,17 @@
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
# Ignore files in git archive (i.e. GitHub release builds)
Dockerfile export-ignore
.travis.yml export-ignore
.github/ export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.dockerignore export-ignore
scalingo.json export-ignore
phpunit.xml export-ignore
phpcs.xml export-ignore
phpcompatibility.xml export-ignore
tests/ export-ignore
cache/.gitkeep export-ignore

49
.github/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,49 @@
### Pull request policy
* [Fix one issue per pull request](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#fix-one-issue-per-pull-request)
* [Respect the coding style policy](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#respect-the-coding-style-policy)
* [Properly name your commits](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#properly-name-your-commits)
* When fixing a bridge (located in the `bridges` directory), write `[BridgeName] Feature` <br>(i.e. `[YoutubeBridge] Fix typo in video titles`).
* When fixing other files, use `[FileName] Feature` <br>(i.e. `[index.php] Add multilingual support`).
* When fixing a general problem that applies to multiple files, write `category: feature` <br>(i.e. `bridges: Fix various typos`).
Note that all pull-requests must pass all tests before they can be merged.
### Coding style
* [Whitespace](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace)
* [Add a new line at the end of a file](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#add-a-new-line-at-the-end-of-a-file)
* [Do not add a whitespace before a semicolon](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#add-a-new-line-at-the-end-of-a-file)
* [Do not add whitespace at start or end of a file or end of a line](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#do-not-add-whitespace-at-start-or-end-of-a-file-or-end-of-a-line)
* [Indentation](https://github.com/RSS-Bridge/rss-bridge/wiki/Indentation)
* [Use tabs for indentation](https://github.com/RSS-Bridge/rss-bridge/wiki/Indentation#use-tabs-for-indentation)
* [Maximum line length](https://github.com/RSS-Bridge/rss-bridge/wiki/Maximum-line-length)
* [The maximum line length should not exceed 80 characters](https://github.com/RSS-Bridge/rss-bridge/wiki/Maximum-line-length#the-maximum-line-length-should-not-exceed-80-characters)
* [Strings](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings)
* [Whenever possible use single quoted strings](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#whenever-possible-use-single-quote-strings)
* [Add spaces around the concatenation operator](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#add-spaces-around-the-concatenation-operator)
* [Use a single string instead of concatenating](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#use-a-single-string-instead-of-concatenating)
* [Constants](https://github.com/RSS-Bridge/rss-bridge/wiki/Constants)
* [Use UPPERCASE for constants](https://github.com/RSS-Bridge/rss-bridge/wiki/Constants#use-uppercase-for-constants)
* [Keywords](https://github.com/RSS-Bridge/rss-bridge/wiki/Keywords)
* [Use lowercase for `true`, `false` and `null`](https://github.com/RSS-Bridge/rss-bridge/wiki/Keywords#use-lowercase-for-true-false-and-null)
* [Operators](https://github.com/RSS-Bridge/rss-bridge/wiki/Operators)
* [Operators must have a space around them](https://github.com/RSS-Bridge/rss-bridge/wiki/Operators#operators-must-have-a-space-around-them)
* [Functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions)
* [Parameters with default values must appear last in functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#parameters-with-default-values-must-appear-last-in-functions)
* [Calling functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#calling-functions)
* [Do not add spaces after opening or before closing bracket](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#do-not-add-spaces-after-opening-or-before-closing-bracket)
* [Structures](https://github.com/RSS-Bridge/rss-bridge/wiki/Structures)
* [Structures must always be formatted as multi-line blocks](https://github.com/RSS-Bridge/rss-bridge/wiki/Structures#structures-must-always-be-formatted-as-multi-line-blocks)
* [If-Statement](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement)
* [Use `elseif` instead of `else if`](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#use-elseif-instead-of-else-if)
* [Do not write empty statements](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#do-not-write-empty-statements)
* [Do not write unconditional if-statements](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#do-not-write-unconditional-if-statements)
* [Classes](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes)
* [Use PascalCase for class names](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#use-pascalcase-for-class-names)
* [Do not use final statements inside final classes](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-use-final-statements-inside-final-classes)
* [Do not override methods to call their parent](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-override-methods-to-call-their-parent)
* [abstract and final declarations MUST precede the visibility declaration](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#abstract-and-final-declarations-must-precede-the-visibility-declaration)
* [static declaration MUST come after the visibility declaration](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#static-declaration-must-come-after-the-visibility-declaration)
* [Casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting)
* [Do not add spaces when casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting#do-not-add-spaces-when-casting)

View File

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

View File

@@ -1,21 +1,35 @@
dist: trusty
sudo: false
language: php
install:
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
composer global require squizlabs/PHP_CodeSniffer;
else
pear channel-update pear.php.net;
pear install PHP_CodeSniffer;
- composer global require dealerdirect/phpcodesniffer-composer-installer;
- composer global require phpcompatibility/php-compatibility;
# Use PHPUnit 6 for unit tests (stable), requires PHP 7
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
composer global require phpunit/phpunit ^6;
fi
# Use latest PHPUnit on nightly to detect breaking changes
- if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then
composer global require phpunit/phpunit;
fi
script:
- phpenv rehash
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
/home/travis/.composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
else
phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
# Run PHP_CodeSniffer on all versions
- ~/.config/composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
# Check PHP compatibility for the lowest supported version
- if [[ $TRAVIS_PHP_VERSION == "5.6" ]]; then
~/.config/composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --extensions=php -p;
fi
# Run unit tests (stable)
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
phpunit --configuration=phpunit.xml --include-path=lib/;
fi
# Run unit tests (latest/nightly)
# Check PHP compatibility for all versions, starting at the lowest supported version in order to detect breaking changes
- if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then
phpunit --configuration=phpunit.xml --include-path=lib/;
~/.config/composer/vendor/bin/phpcs . --standard=PHPCompatibility --extensions=php -p --runtime-set testVersion 5.6-;
fi
matrix:
@@ -24,9 +38,7 @@ matrix:
include:
- php: 5.6
- php: 7.0
- php: hhvm
- php: nightly
allow_failures:
- php: hhvm
- php: nightly

View File

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

View File

@@ -1,47 +0,0 @@
### Pull request policy
Fix one issue per pull request.
Squash commits before opening a pull request.
Respect the coding style policy.
Name your PR like the following :
* When correcting a single bridge, use `[BridgeName] Feature`.
* When fixing a problem in a specific file, use `[FileName] Feature`.
* When fixing a general problem, use `category : feature`.
Note that all pull-requests should pass the unit tests before they can be merged.
### Coding style
Use `camelCase` for variables and methods.
Use `UPPERCASE` for constants.
Use `PascalCase` for class names. When creating a bridge, your class and PHP file should be named `MyImplementationBridge`.
Use tabs for indentation.
Add an empty line at the end of your file.
Use `''` to encapsulate strings, including in arrays.
Prefer lines shorter than 80 chars, no line longer than 120 chars.
PHP constants should be in lower case (`true, false, null`...)
* Add spaces between the logical operator and your expressions (not needed for the `!` operator).
* Use `||` and `&&` instead of `or` and `and`.
* Add space between your condition and the opening bracket/closing bracket.
* Don't put a space between `if` and your bracket.
* Use `elseif` instead of `else if`.
* Add new lines in your conditions if they are containing more than one line.
* Example :
```PHP
if($a == true && $b) {
print($a);
} else if(!$b) {
$a = !$a;
$b = $b >> $a;
print($b);
} else {
print($b);
}
```

236
README.md
View File

@@ -1,10 +1,12 @@
rss-bridge
===
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE) [![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg)](https://github.com/rss-bridge/rss-bridge/releases/latest) [![Build Status](https://travis-ci.org/RSS-Bridge/rss-bridge.svg?branch=master)](https://travis-ci.org/RSS-Bridge/rss-bridge) [![Docker Build Status](https://img.shields.io/docker/build/rssbridge/rss-bridge.svg)](https://hub.docker.com/r/rssbridge/rss-bridge/)
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE) [![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg)](https://github.com/rss-bridge/rss-bridge/releases/latest) [![Debian Release](https://img.shields.io/badge/dynamic/json.svg?label=debian%20release&url=https%3A%2F%2Fsources.debian.org%2Fapi%2Fsrc%2Frss-bridge%2F&query=%24.versions%5B0%5D.version&colorB=blue)](https://tracker.debian.org/pkg/rss-bridge) [![Guix Release](https://img.shields.io/badge/guix%20release-unknown-light--gray.svg)](https://www.gnu.org/software/guix/packages/R/) [![Build Status](https://travis-ci.org/RSS-Bridge/rss-bridge.svg?branch=master)](https://travis-ci.org/RSS-Bridge/rss-bridge) [![Docker Build Status](https://img.shields.io/docker/build/rssbridge/rss-bridge.svg)](https://hub.docker.com/r/rssbridge/rss-bridge/)
rss-bridge is a PHP project capable of generating ATOM feeds for websites which don't have one.
RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites which don't have one. It can be used on webservers or as stand alone application in CLI mode.
Supported sites/pages (main)
**Important**: RSS-Bridge is __not__ a feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators).
Supported sites/pages (examples)
===
* `Bandcamp` : Returns last release from [bandcamp](https://bandcamp.com/) for a tag
@@ -25,106 +27,198 @@ Supported sites/pages (main)
* `Wikipedia`: highlighted articles from [Wikipedia](https://wikipedia.org/) in English, German, French or Esperanto
* `YouTube` : YouTube user channel, playlist or search
Plus [many other bridges](bridges/) to enable, thanks to the community
And [many more](bridges/), thanks to the community!
Output format
===
Output format can take several forms:
* `Atom` : ATOM Feed, for use in RSS/Feed readers
* `Html` : Simple html page.
* `Json` : Json, for consumption by other applications.
* `Mrss` : MRSS Feed, for use in RSS/Feed readers
* `Plaintext` : raw text (php object, as returned by print_r)
RSS-Bridge is capable of producing several output formats:
* `Atom` : Atom feed, for use in feed readers
* `Html` : Simple HTML page
* `Json` : JSON, for consumption by other applications
* `Mrss` : MRSS feed, for use in feed readers
* `Plaintext` : Raw text, for consumption by other applications
You can extend RSS-Bridge with your own format, using the [Format API](https://github.com/RSS-Bridge/rss-bridge/wiki/Format-API)!
Screenshot
===
Welcome screen:
![Screenshot](https://github.com/RSS-Bridge/rss-bridge/wiki/images/screenshot_rss-bridge_welcome.png)
RSS-Bridge hashtag (#rss-bridge) search on Twitter, in ATOM format (as displayed by Firefox):
***
RSS-Bridge hashtag (#rss-bridge) search on Twitter, in Atom format (as displayed by Firefox):
![Screenshot](https://github.com/RSS-Bridge/rss-bridge/wiki/images/screenshot_twitterbridge_atom.png)
Requirements
===
* PHP 5.6, e.g. `AddHandler application/x-httpd-php56 .php` in `.htaccess`
* `openssl` extension enabled in PHP config (`php.ini`)
* `curl` extension enabled in PHP config (`php.ini`)
RSS-Bridge requires PHP 5.6 or higher with following extensions enabled:
Enabling/Disabling bridges
- [`openssl`](https://secure.php.net/manual/en/book.openssl.php)
- [`libxml`](https://secure.php.net/manual/en/book.libxml.php)
- [`mbstring`](https://secure.php.net/manual/en/book.mbstring.php)
- [`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)
Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
Enable / Disable bridges
===
By default, the script creates `whitelist.txt` and adds the main bridges (see above). `whitelist.txt` is ignored by git, you can edit it:
* to enable extra bridges (one bridge per line)
* to disable main bridges (remove the line)
* to enable all bridges (just one wildcard `*` as file content)
RSS-Bridge allows you to take full control over which bridges are displayed to the user. That way you can host your own RSS-Bridge service with your favorite collection of bridges!
New bridges are disabled by default, so make sure to check regularly what's new and whitelist what you want!
Find more information on the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting)
**Notice**: By default RSS-Bridge will only show a small subset of bridges. Make sure to read up on [whitelisting](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting) to unlock the full potential of RSS-Bridge!
Deploy
===
Thanks to the community, hosting your own instance of RSS-Bridge is as easy as clicking a button!
[![Deploy on Scalingo](https://cdn.scalingo.com/deploy/button.svg)](https://my.scalingo.com/deploy?source=https://github.com/sebsauvage/rss-bridge)
[![Deploy to Docker Cloud](https://files.cloud.docker.com/images/deploy-to-dockercloud.svg)](https://cloud.docker.com/stack/deploy/?repo=https://github.com/rss-bridge/rss-bridge)
Getting involved
===
There are many ways for you to getting involved with RSS-Bridge. Here are a few things:
- Share RSS-Bridge with your friends (Twitter, Facebook, ..._you name it_...)
- Report broken bridges or bugs by opening [Issues](https://github.com/RSS-Bridge/rss-bridge/issues) on GitHub
- Request new features or suggest ideas (via [Issues](https://github.com/RSS-Bridge/rss-bridge/issues))
- Discuss bugs, features, ideas or [issues](https://github.com/RSS-Bridge/rss-bridge/issues)
- Add new bridges or improve the API
- Improve the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki)
- Host an instance of RSS-Bridge for your personal use or make it available to the community :sparkling_heart:
Authors
===
We are RSS Bridge Community, a group of developers continuing the project initiated by sebsauvage, webmaster of [sebsauvage.net](http://sebsauvage.net), author of [Shaarli](http://sebsauvage.net/wiki/doku.php?id=php:shaarli) and [ZeroBin](http://sebsauvage.net/wiki/doku.php?id=php:zerobin).
Patch/contributors :
We are RSS-Bridge community, a group of developers continuing the project initiated by sebsauvage, webmaster of [sebsauvage.net](http://sebsauvage.net), author of [Shaarli](http://sebsauvage.net/wiki/doku.php?id=php:shaarli) and [ZeroBin](http://sebsauvage.net/wiki/doku.php?id=php:zerobin).
* Yves ASTIER ([Draeli](https://github.com/Draeli)) : PHP optimizations, fixes, dynamic brigde/format list with all stuff behind and extend cache system. Mail : contact /at\ yves-astier.com
* [Mitsukarenai](https://github.com/Mitsukarenai) : Initial inspiration, collaborator
* [ArthurHoaro](https://github.com/ArthurHoaro)
* [BoboTiG](https://github.com/BoboTiG)
* [Astalaseven](https://github.com/Astalaseven)
* [qwertygc](https://github.com/qwertygc)
* [Djuuu](https://github.com/Djuuu)
* [Anadrark](https://github.com/Anadrark])
* [Grummfy](https://github.com/Grummfy)
* [Polopollo](https://github.com/Polopollo)
* [16mhz](https://github.com/16mhz)
* [kranack](https://github.com/kranack)
* [logmanoriginal](https://github.com/logmanoriginal)
* [polo2ro](https://github.com/polo2ro)
* [Riduidel](https://github.com/Riduidel)
* [superbaillot.net](http://superbaillot.net/)
* [vinzv](https://github.com/vinzv)
* [teromene](https://github.com/teromene)
* [nel50n](https://github.com/nel50n)
* [nyutag](https://github.com/nyutag)
* [ORelio](https://github.com/ORelio)
* [Pitchoule](https://github.com/Pitchoule)
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
* [aledeg](https://github.com/aledeg)
* [alexAubin](https://github.com/alexAubin)
* [cnlpete](https://github.com/cnlpete)
* [corenting](https://github.com/corenting)
* [Daiyousei](https://github.com/Daiyousei)
* [erwang](https://github.com/erwang)
* [gsurrel](https://github.com/gsurrel)
* [kraoc](https://github.com/kraoc)
* [lagaisse](https://github.com/lagaisse)
* [az5he6ch](https://github.com/az5he6ch)
* [niawag](https://github.com/niawag)
* [JeremyRand](https://github.com/JeremyRand)
* [mro](https://github.com/mro)
**Contributors** (sorted alphabetically):
<!--
Use this script to generate the list automatically (using the GitHub API):
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
-->
* [16mhz](https://github.com/16mhz)
* [Ahiles3005](https://github.com/Ahiles3005)
* [Albirew](https://github.com/Albirew)
* [AmauryCarrade](https://github.com/AmauryCarrade)
* [AntoineTurmel](https://github.com/AntoineTurmel)
* [ArthurHoaro](https://github.com/ArthurHoaro)
* [Astalaseven](https://github.com/Astalaseven)
* [Astyan-42](https://github.com/Astyan-42)
* [Daiyousei](https://github.com/Daiyousei)
* [Djuuu](https://github.com/Djuuu)
* [Draeli](https://github.com/Draeli)
* [EtienneM](https://github.com/EtienneM)
* [Frenzie](https://github.com/Frenzie)
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
* [Glandos](https://github.com/Glandos)
* [GregThib](https://github.com/GregThib)
* [Grummfy](https://github.com/Grummfy)
* [JackNUMBER](https://github.com/JackNUMBER)
* [JeremyRand](https://github.com/JeremyRand)
* [Jocker666z](https://github.com/Jocker666z)
* [LogMANOriginal](https://github.com/LogMANOriginal)
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
* [Nono-m0le](https://github.com/Nono-m0le)
* [ORelio](https://github.com/ORelio)
* [PaulVayssiere](https://github.com/PaulVayssiere)
* [Piranhaplant](https://github.com/Piranhaplant)
* [Riduidel](https://github.com/Riduidel)
* [Roliga](https://github.com/Roliga)
* [Strubbl](https://github.com/Strubbl)
* [TheRadialActive](https://github.com/TheRadialActive)
* [TwizzyDizzy](https://github.com/TwizzyDizzy)
* [WalterBarrett](https://github.com/WalterBarrett)
* [ZeNairolf](https://github.com/ZeNairolf)
* [adamchainz](https://github.com/adamchainz)
* [aledeg](https://github.com/aledeg)
* [alexAubin](https://github.com/alexAubin)
* [az5he6ch](https://github.com/az5he6ch)
* [b1nj](https://github.com/b1nj)
* [benasse](https://github.com/benasse)
* [captn3m0](https://github.com/captn3m0)
* [chemel](https://github.com/chemel)
* [ckiw](https://github.com/ckiw)
* [cnlpete](https://github.com/cnlpete)
* [corenting](https://github.com/corenting)
* [couraudt](https://github.com/couraudt)
* [da2x](https://github.com/da2x)
* [disk0x](https://github.com/disk0x)
* [eMerzh](https://github.com/eMerzh)
* [em92](https://github.com/em92)
* [fluffy-critter](https://github.com/fluffy-critter)
* [fulmeek](https://github.com/fulmeek)
* [griffaurel](https://github.com/griffaurel)
* [hunhejj](https://github.com/hunhejj)
* [j0k3r](https://github.com/j0k3r)
* [jdigilio](https://github.com/jdigilio)
* [kranack](https://github.com/kranack)
* [kraoc](https://github.com/kraoc)
* [laBecasse](https://github.com/laBecasse)
* [lagaisse](https://github.com/lagaisse)
* [lalannev](https://github.com/lalannev)
* [ldidry](https://github.com/ldidry)
* [lorenzos](https://github.com/lorenzos)
* [m0zes](https://github.com/m0zes)
* [matthewseal](https://github.com/matthewseal)
* [mcbyte-it](https://github.com/mcbyte-it)
* [mdemoss](https://github.com/mdemoss)
* [melangue](https://github.com/melangue)
* [metaMMA](https://github.com/metaMMA)
* [mickael-bertrand](https://github.com/mickael-bertrand)
* [mitsukarenai](https://github.com/mitsukarenai)
* [mr-flibble](https://github.com/mr-flibble)
* [mro](https://github.com/mro)
* [mxmehl](https://github.com/mxmehl)
* [nel50n](https://github.com/nel50n)
* [niawag](https://github.com/niawag)
* [pellaeon](https://github.com/pellaeon)
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
* [pitchoule](https://github.com/pitchoule)
* [pmaziere](https://github.com/pmaziere)
* [prysme01](https://github.com/prysme01)
* [quentinus95](https://github.com/quentinus95)
* [qwertygc](https://github.com/qwertygc)
* [regisenguehard](https://github.com/regisenguehard)
* [rogerdc](https://github.com/rogerdc)
* [sebsauvage](https://github.com/sebsauvage)
* [sublimz](https://github.com/sublimz)
* [sysadminstory](https://github.com/sysadminstory)
* [tameroski](https://github.com/tameroski)
* [teromene](https://github.com/teromene)
* [triatic](https://github.com/triatic)
* [wtuuju](https://github.com/wtuuju)
* [yardenac](https://github.com/yardenac)
Licenses
===
Code is [Public Domain](UNLICENSE).
Including `PHP Simple HTML DOM Parser` under the [MIT License](http://opensource.org/licenses/MIT)
The source code for RSS-Bridge is [Public Domain](UNLICENSE).
RSS-Bridge uses third party libraries with their own license:
* [`PHP Simple HTML DOM Parser`](http://simplehtmldom.sourceforge.net/) licensed under the [MIT License](http://opensource.org/licenses/MIT)
* [`php-urljoin`](https://github.com/fluffy-critter/php-urljoin) licensed under the [MIT License](http://opensource.org/licenses/MIT)
Technical notes
===
* There is a cache so that source services won't ban you even if you hammer the rss-bridge with requests. Each bridge can have a different duration for the cache. The `cache` subdirectory will be automatically created and cached objects older than 24 hours get purged.
* To implement a new Bridge, [follow the specifications](https://github.com/RSS-Bridge/rss-bridge/wiki/Bridge-API) and take a look at existing Bridges for examples.
* To enable debug mode (disabling cache and enabling error reporting), create an empty file named `DEBUG` in the root directory (next to `index.php`).
* For more information refer to the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki)
* RSS-Bridge uses caching to prevent services from banning your server for repeatedly updating feeds. The specific cache duration can be different between bridges. Cached files are deleted automatically after 24 hours.
* You can implement your own bridge, [following these instructions](https://github.com/RSS-Bridge/rss-bridge/wiki/Bridge-API).
* You can enable debug mode to disable caching. Find more information on the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki/Debug-mode)
Rant
===
@@ -133,10 +227,10 @@ Rant
Your catchword is "share", but you don't want us to share. You want to keep us within your walled gardens. That's why you've been removing RSS links from webpages, hiding them deep on your website, or removed feeds entirely, replacing it with crippled or demented proprietary API. **FUCK YOU.**
You're not social when you hamper sharing by removing feeds. You're happy to have customers creating content for your ecosystem, but you don't want this content out - a content you do not even own. Google Takeout is just a gimmick. We want our data to flow, we want RSS or ATOM feeds.
You're not social when you hamper sharing by removing feeds. You're happy to have customers creating content for your ecosystem, but you don't want this content out - a content you do not even own. Google Takeout is just a gimmick. We want our data to flow, we want RSS or Atom feeds.
We want to share with friends, using open protocols: RSS, ATOM, XMPP, whatever. Because no one wants to have *your* service with *your* applications using *your* API force-feeding them. Friends must be free to choose whatever software and service they want.
We want to share with friends, using open protocols: RSS, Atom, XMPP, whatever. Because no one wants to have *your* service with *your* applications using *your* API force-feeding them. Friends must be free to choose whatever software and service they want.
We are rebuilding bridges you have wilfully destroyed.
Get your shit together: Put RSS/ATOM back in.
Get your shit together: Put RSS/Atom back in.

View File

@@ -8,7 +8,7 @@ class ABCTabsBridge extends BridgeAbstract {
public function collectData(){
$html = '';
$html = getSimpleHTMLDOM(static::URI.'tablatures/nouveautes.html')
$html = getSimpleHTMLDOM(static::URI . 'tablatures/nouveautes.html')
or returnClientError('No results for this query.');
$table = $html->find('table#myTable', 0)->children(1);

View File

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

View File

@@ -45,7 +45,7 @@ class AllocineFRBridge extends BridgeAbstract {
public function getName(){
if(!is_null($this->getInput('category'))) {
return self::NAME . ' : '
.array_search(
. array_search(
$this->getInput('category'),
self::PARAMETERS[$this->queriedContext]['category']['values']
);
@@ -83,5 +83,4 @@ class AllocineFRBridge extends BridgeAbstract {
}
}
}
}

View File

@@ -52,7 +52,7 @@ class AmazonBridge extends BridgeAbstract {
public function getName(){
if(!is_null($this->getInput('tld')) && !is_null($this->getInput('q'))) {
return 'Amazon.'.$this->getInput('tld').': '.$this->getInput('q');
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('q');
}
return parent::getName();
@@ -60,8 +60,8 @@ class AmazonBridge extends BridgeAbstract {
public function collectData() {
$uri = 'https://www.amazon.'.$this->getInput('tld').'/';
$uri .= 's/?field-keywords='.urlencode($this->getInput('q')).'&sort='.$this->getInput('sort');
$uri = 'https://www.amazon.' . $this->getInput('tld') . '/';
$uri .= 's/?field-keywords=' . urlencode($this->getInput('q')) . '&sort=' . $this->getInput('sort');
$html = getSimpleHTMLDOM($uri)
or returnServerError('Could not request Amazon.');
@@ -72,6 +72,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);
@@ -86,7 +89,7 @@ class AmazonBridge extends BridgeAbstract {
$price = $element->find('span.s-price', 0);
$price = ($price) ? $price->innertext : '';
$item['content'] = '<img src="'.$image->getAttribute('src').'" /><br />'.$price;
$item['content'] = '<img src="' . $image->getAttribute('src') . '" /><br />' . $price;
$this->items[] = $item;
}

207
bridges/AnidexBridge.php Normal file
View File

@@ -0,0 +1,207 @@
<?php
class AnidexBridge extends BridgeAbstract {
const MAINTAINER = 'ORelio';
const NAME = 'Anidex';
const URI = 'https://anidex.info/';
const DESCRIPTION = 'Returns the newest torrents, with optional search criteria.';
const PARAMETERS = array(
array(
'id' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All categories' => '0',
'Anime' => '1,2,3',
'Anime - Sub' => '1',
'Anime - Raw' => '2',
'Anime - Dub' => '3',
'Live Action' => '4,5',
'Live Action - Sub' => '4',
'Live Action - Raw' => '5',
'Light Novel' => '6',
'Manga' => '7,8',
'Manga - Translated' => '7',
'Manga - Raw' => '8',
'Music' => '9,10,11',
'Music - Lossy' => '9',
'Music - Lossless' => '10',
'Music - Video' => '11',
'Games' => '12',
'Applications' => '13',
'Pictures' => '14',
'Adult Video' => '15',
'Other' => '16'
)
),
'lang_id' => array(
'name' => 'Language',
'type' => 'list',
'values' => array(
'All languages' => '0',
'English' => '1',
'Japanese' => '2',
'Polish' => '3',
'Serbo-Croatian' => '4',
'Dutch' => '5',
'Italian' => '6',
'Russian' => '7',
'German' => '8',
'Hungarian' => '9',
'French' => '10',
'Finnish' => '11',
'Vietnamese' => '12',
'Greek' => '13',
'Bulgarian' => '14',
'Spanish (Spain)' => '15',
'Portuguese (Brazil)' => '16',
'Portuguese (Portugal)' => '17',
'Swedish' => '18',
'Arabic' => '19',
'Danish' => '20',
'Chinese (Simplified)' => '21',
'Bengali' => '22',
'Romanian' => '23',
'Czech' => '24',
'Mongolian' => '25',
'Turkish' => '26',
'Indonesian' => '27',
'Korean' => '28',
'Spanish (LATAM)' => '29',
'Persian' => '30',
'Malaysian' => '31'
)
),
'group_id' => array(
'name' => 'Group ID',
'type' => 'number'
),
'r' => array(
'name' => 'Hide Remakes',
'type' => 'checkbox'
),
'b' => array(
'name' => 'Only Batches',
'type' => 'checkbox'
),
'a' => array(
'name' => 'Only Authorized',
'type' => 'checkbox'
),
'q' => array(
'name' => 'Keyword',
'description' => 'Keyword(s)',
'type' => 'text'
),
'h' => array(
'name' => 'Adult content',
'type' => 'list',
'values' => array(
'No filter' => '0',
'Hide +18' => '1',
'Only +18' => '2'
)
)
)
);
public function collectData() {
// Build Search URL from user-provided parameters
$search_url = self::URI . '?s=upload_timestamp&o=desc';
foreach (array('id', 'lang_id', 'group_id') as $param_name) {
$param = $this->getInput($param_name);
if (!empty($param) && intval($param) != 0 && ctype_digit(str_replace(',', '', $param))) {
$search_url .= '&' . $param_name . '=' . $param;
}
}
foreach (array('r', 'b', 'a') as $param_name) {
$param = $this->getInput($param_name);
if (!empty($param) && boolval($param)) {
$search_url .= '&' . $param_name . '=1';
}
}
$query = $this->getInput('q');
if (!empty($query)) {
$search_url .= '&q=' . urlencode($query);
}
$opt = array();
$h = $this->getInput('h');
if (!empty($h) && intval($h) != 0 && ctype_digit($h)) {
$opt[CURLOPT_COOKIE] = 'anidex_h_toggle=' . $h;
}
// Retrieve torrent listing from search results, which does not contain torrent description
$html = getSimpleHTMLDOM($search_url, array(), $opt)
or returnServerError('Could not request Anidex: ' . $search_url);
$links = $html->find('a');
$results = array();
foreach ($links as $link)
if (strpos($link->href, '/torrent/') === 0 && !in_array($link->href, $results))
$results[] = $link->href;
if (empty($results) && empty($this->getInput('q')))
returnServerError('No results from Anidex: ' . $search_url);
//Process each item individually
foreach ($results as $element) {
//Limit total amount of requests
if(count($this->items) >= 20) {
break;
}
$torrent_id = str_replace('/torrent/', '', $element);
//Ignore entries without valid torrent ID
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
//Retrieve data for this torrent ID
$item_uri = self::URI . 'torrent/' . $torrent_id;
//Retrieve full description from torrent page
if ($item_html = getSimpleHTMLDOMCached($item_uri)) {
//Retrieve data from page contents
$item_title = str_replace(' (Torrent) - AniDex ', '', $item_html->find('title', 0)->plaintext);
$item_desc = $item_html->find('div.panel-body', 0);
$item_author = trim($item_html->find('span.fa-user', 0)->parent()->plaintext);
$item_date = strtotime(trim($item_html->find('span.fa-clock', 0)->parent()->plaintext));
$item_image = $this->getURI() . 'images/user_logos/default.png';
//Check for description-less torrent andn optionally extract image
$desc_title_found = false;
foreach ($item_html->find('h3.panel-title') as $h3) {
if (strpos($h3, 'Description') !== false) {
$desc_title_found = true;
break;
}
}
if ($desc_title_found) {
//Retrieve image for thumbnail or generic logo fallback
foreach ($item_desc->find('img') as $img) {
if (strpos($img->src, 'prez') === false) {
$item_image = $img->src;
break;
}
}
$item_desc = trim($item_desc->innertext);
} else {
$item_desc = '<em>No description.</em>';
}
//Build and add final item
$item = array();
$item['uri'] = $item_uri;
$item['title'] = $item_title;
$item['author'] = $item_author;
$item['timestamp'] = $item_date;
$item['enclosures'] = array($item_image);
$item['content'] = $item_desc;
$this->items[] = $item;
}
}
$element = null;
}
$results = null;
}
}

View File

@@ -5,7 +5,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
const NAME = 'Anime-Ultime';
const URI = 'http://www.anime-ultime.net/';
const CACHE_TIMEOUT = 10800; // 3h
const DESCRIPTION = 'Returns the 10 newest releases posted on Anime-Ultime';
const DESCRIPTION = 'Returns the newest releases posted on Anime-Ultime.';
const PARAMETERS = array( array(
'type' => array(
'name' => 'Type',
@@ -65,6 +65,13 @@ class AnimeUltimeBridge extends BridgeAbstract {
$item_link_element = $release->find('td', 0)->find('a', 0);
$item_uri = self::URI . $item_link_element->href;
$item_name = html_entity_decode($item_link_element->plaintext);
$item_image = self::URI . substr(
$item_link_element->onmouseover,
37,
strpos($item_link_element->onmouseover, ' ', 37) - 37
);
$item_episode = html_entity_decode(
str_pad(
$release->find('td', 1)->plaintext,
@@ -79,8 +86,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
if(!empty($item_uri)) {
// Retrieve description from description page and
// convert relative image src info absolute image src
// Retrieve description from description page
$html_item = getContents($item_uri)
or returnServerError('Could not request Anime-Ultime: ' . $item_uri);
$item_description = substr(
@@ -91,10 +97,9 @@ class AnimeUltimeBridge extends BridgeAbstract {
0,
strpos($item_description, '<div id="table">')
);
$item_description = str_replace(
'src="images', 'src="' . self::URI . 'images',
$item_description
);
// Convert relative image src into absolute image src, remove line breaks
$item_description = defaultLinkTo($item_description, self::URI);
$item_description = str_replace("\r", '', $item_description);
$item_description = str_replace("\n", '', $item_description);
$item_description = utf8_encode($item_description);
@@ -105,6 +110,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
$item['title'] = $item_name . ' ' . $item_type . ' ' . $item_episode;
$item['author'] = $item_fansub;
$item['timestamp'] = $item_date;
$item['enclosures'] = array($item_image);
$item['content'] = $item_description;
$this->items[] = $item;
$processedOK++;
@@ -131,5 +137,4 @@ class AnimeUltimeBridge extends BridgeAbstract {
return parent::getName();
}
}

View File

@@ -28,6 +28,13 @@ class Arte7Bridge extends BridgeAbstract {
)
)
),
'Collection (Français)' => array(
'colfr' => array(
'name' => 'Collection id',
'required' => true,
'title' => 'ex. RC-014095 pour https://www.arte.tv/fr/videos/RC-014095/blow-up/'
)
),
'Catégorie (Allemand)' => array(
'catde' => array(
'type' => 'list',
@@ -45,6 +52,13 @@ class Arte7Bridge extends BridgeAbstract {
'Sonstiges' => 'AUT'
)
)
),
'Collection (Allemand)' => array(
'colde' => array(
'name' => 'Collection id',
'required' => true,
'title' => 'ex. RC-014095 pour https://www.arte.tv/de/videos/RC-014095/blow-up/'
)
)
);
@@ -54,15 +68,24 @@ class Arte7Bridge extends BridgeAbstract {
$category = $this->getInput('catfr');
$lang = 'fr';
break;
case 'Collection (Français)':
$lang = 'fr';
$collectionId = $this->getInput('colfr');
break;
case 'Catégorie (Allemand)':
$category = $this->getInput('catde');
$lang = 'de';
break;
case 'Collection (Allemand)':
$lang = 'de';
$collectionId = $this->getInput('colde');
break;
}
$url = 'https://api.arte.tv/api/opa/v3/videos?sort=-lastModified&limit=10&language='
. $lang
. ($category != null ? '&category.code=' . $category : '');
. ($category != null ? '&category.code=' . $category : '')
. ($collectionId != null ? '&collections.collectionId=' . $collectionId : '');
$header = array(
'Authorization: Bearer ' . self::API_TOKEN
@@ -96,5 +119,4 @@ class Arte7Bridge extends BridgeAbstract {
$this->items[] = $item;
}
}
}

View File

@@ -1,7 +1,7 @@
<?php
class AskfmBridge extends BridgeAbstract {
const MAINTAINER = 'az5he6ch';
const MAINTAINER = 'az5he6ch, logmanoriginal';
const NAME = 'Ask.fm Answers';
const URI = 'https://ask.fm/';
const CACHE_TIMEOUT = 300; //5 min
@@ -19,39 +19,39 @@ class AskfmBridge extends BridgeAbstract {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Requested username can\'t be found.');
foreach($html->find('div.streamItem-answer') as $element) {
$html = defaultLinkTo($html, self::URI);
foreach($html->find('article.streamItem-answer') as $element) {
$item = array();
$item['uri'] = self::URI . $element->find('a.streamItemsAge', 0)->href;
$question = trim($element->find('h1.streamItemContent-question', 0)->innertext);
$item['uri'] = $element->find('a.streamItem_meta', 0)->href;
$question = trim($element->find('header.streamItem_header', 0)->innertext);
$item['title'] = trim(
htmlspecialchars_decode($element->find('h1.streamItemContent-question', 0)->plaintext,
htmlspecialchars_decode($element->find('header.streamItem_header', 0)->plaintext,
ENT_QUOTES
)
);
$answer = trim($element->find('p.streamItemContent-answer', 0)->innertext);
$item['timestamp'] = strtotime($element->find('time', 0)->datetime);
// Doesn't work, DOM parser doesn't seem to like data-hint, dunno why
#$item['update'] = $element->find('a.streamitemsage',0)->data-hint;
$answer = trim($element->find('div.streamItem_content', 0)->innertext);
// This probably should be cleaned up, especially for YouTube embeds
$visual = $element->find('div.streamItemContent-visual', 0)->innertext;
//Fix tracking links, also doesn't work
if($visual = $element->find('div.streamItem_visual', 0)) {
$visual = $visual->innertext;
}
// Fix tracking links, also doesn't work
foreach($element->find('a') as $link) {
if(strpos($link->href, 'l.ask.fm') !== false) {
// Too slow
#$link->href = str_replace('#_=_', '', get_headers($link->href, 1)['Location']);
$link->href = $link->plaintext;
}
}
$content = '<p>' . $question . '</p><p>' . $answer . '</p><p>' . $visual . '</p>';
// Fix relative links without breaking // scheme used by YouTube stuff
$content = preg_replace('#href="\/(?!\/)#', 'href="' . self::URI, $content);
$item['content'] = $content;
$item['content'] = '<p>' . $question
. '</p><p>' . $answer
. '</p><p>' . $visual . '</p>';
$this->items[] = $item;
}
}
@@ -66,7 +66,7 @@ class AskfmBridge extends BridgeAbstract {
public function getURI(){
if(!is_null($this->getInput('u'))) {
return self::URI . urlencode($this->getInput('u')) . '/answers/more?page=0';
return self::URI . urlencode($this->getInput('u'));
}
return parent::getURI();

65
bridges/AutoJMBridge.php Normal file
View File

@@ -0,0 +1,65 @@
<?php
class AutoJMBridge extends BridgeAbstract {
const NAME = 'AutoJM';
const URI = 'http://www.autojm.fr/';
const DESCRIPTION = 'Suivre les offres de véhicules proposés par AutoJM en fonction des critères de filtrages';
const MAINTAINER = 'sysadminstory';
const PARAMETERS = array(
'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM' => array(
'url' => array(
'name' => 'URL de la recherche',
'type' => 'text',
'required' => true,
'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/',
'exampleValue' => 'gammes/index/398?order_by=finition_asc&energie[]=3&transmission[]=2&dispo=all'
)
)
);
const CACHE_TIMEOUT = 3600;
public function getIcon() {
return self::URI . 'assets/images/favicon.ico';
}
public function collectData() {
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
or returnServerError('Could not request AutoJM.');
$list = $html->find('div[class*=ligne_modele]');
foreach($list as $element) {
$image = $element->find('img[class=width-100]', 0)->src;
$serie = $element->find('div[class=serie]', 0)->find('span', 0)->plaintext;
$url = $element->find('div[class=serie]', 0)->find('a[class=btn_ligne color-black]', 0)->href;
if($element->find('div[class*=hasStock-info]', 0) != null) {
$dispo = 'Disponible';
} else {
$dispo = 'Sur commande';
}
$carburant = str_replace('dispo |', '', $element->find('div[class=carburant]', 0)->plaintext);
$transmission = $element->find('div[class*=bv]', 0)->plaintext;
$places = $element->find('div[class*=places]', 0)->plaintext;
$portes = $element->find('div[class*=nb_portes]', 0)->plaintext;
$carosserie = $element->find('div[class*=coloris]', 0)->plaintext;
$remise = $element->find('div[class*=remise]', 0)->plaintext;
$prix = $element->find('div[class*=prixjm]', 0)->plaintext;
$item = array();
$item['uri'] = $url;
$item['title'] = $serie;
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />' . $serie . '</p>';
$item['content'] .= '<ul><li>Disponibilité : ' . $dispo . '</li>';
$item['content'] .= '<li>Carburant : ' . $carburant . '</li>';
$item['content'] .= '<li>Transmission : ' . $transmission . '</li>';
$item['content'] .= '<li>Nombre de places : ' . $places . '</li>';
$item['content'] .= '<li>Nombre de portes : ' . $portes . '</li>';
$item['content'] .= '<li>Série : ' . $serie . '</li>';
$item['content'] .= '<li>Carosserie : ' . $carosserie . '</li>';
$item['content'] .= '<li>Remise : ' . $remise . '</li>';
$item['content'] .= '<li>Prix : ' . $prix . '</li></ul>';
$this->items[] = $item;
}
}
}

265
bridges/BAEBridge.php Normal file
View File

@@ -0,0 +1,265 @@
<?php
class BAEBridge extends BridgeAbstract {
const MAINTAINER = 'couraudt';
const NAME = 'Bourse Aux Equipiers Bridge';
const URI = 'https://www.bourse-aux-equipiers.com';
const DESCRIPTION = 'Returns the newest sailing offers.';
const PARAMETERS = array(
array(
'keyword' => array(
'name' => 'Filtrer par mots clés',
'title' => 'Entrez le mot clé à filtrer ici'
),
'type' => array(
'name' => 'Type de recherche',
'title' => 'Afficher seuleument un certain type d\'annonce',
'type' => 'list',
'values' => array(
'Toutes les annonces' => false,
'Les embarquements' => 'boat',
'Les skippers' => 'skipper',
'Les équipiers' => 'crew'
)
)
)
);
public function collectData() {
$url = $this->getURI();
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
$annonces = $html->find('main article');
foreach ($annonces as $annonce) {
$detail = $annonce->find('footer a', 0);
$htmlDetail = getSimpleHTMLDOMCached(parent::getURI() . $detail->href);
if (!$htmlDetail)
continue;
$item = array();
$item['title'] = $annonce->find('header h2', 0)->plaintext;
$item['uri'] = parent::getURI() . $detail->href;
$content = $htmlDetail->find('article p', 0)->innertext;
if (!empty($this->getInput('keyword'))) {
$keyword = $this->remove_accents(strtolower($this->getInput('keyword')));
$cleanTitle = $this->remove_accents(strtolower($item['title']));
if (strpos($cleanTitle, $keyword) === false) {
$cleanContent = $this->remove_accents(strtolower($content));
if (strpos($cleanContent, $keyword) === false) {
continue;
}
}
}
$content .= '<hr>';
$content .= $htmlDetail->find('section', 0)->innertext;
$content = str_replace('src="/', 'src="' . parent::getURI() . '/', $content);
$content = str_replace('href="/', 'href="' . parent::getURI() . '/', $content);
$item['content'] = $content;
$image = $htmlDetail->find('#zoom', 0);
if ($image) {
$item['enclosures'] = array(parent::getURI() . $image->getAttribute('src'));
}
$this->items[] = $item;
}
}
public function getURI() {
$uri = parent::getURI();
if (!empty($this->getInput('type'))) {
if ($this->getInput('type') == 'boat') {
$uri .= '/embarquements.html';
} elseif ($this->getInput('type') == 'skipper') {
$uri .= '/skippers.html';
} else {
$uri .= '/equipiers.html';
}
}
return $uri;
}
private function remove_accents($string) {
$chars = array(
// Decompositions for Latin-1 Supplement
'ª' => 'a', 'º' => 'o',
'À' => 'A', 'Á' => 'A',
'Â' => 'A', 'Ã' => 'A',
'Ä' => 'A', 'Å' => 'A',
'Æ' => 'AE', 'Ç' => 'C',
'È' => 'E', 'É' => 'E',
'Ê' => 'E', 'Ë' => 'E',
'Ì' => 'I', 'Í' => 'I',
'Î' => 'I', 'Ï' => 'I',
'Ð' => 'D', 'Ñ' => 'N',
'Ò' => 'O', 'Ó' => 'O',
'Ô' => 'O', 'Õ' => 'O',
'Ö' => 'O', 'Ù' => 'U',
'Ú' => 'U', 'Û' => 'U',
'Ü' => 'U', 'Ý' => 'Y',
'Þ' => 'TH', 'ß' => 's',
'à' => 'a', 'á' => 'a',
'â' => 'a', 'ã' => 'a',
'ä' => 'a', 'å' => 'a',
'æ' => 'ae', 'ç' => 'c',
'è' => 'e', 'é' => 'e',
'ê' => 'e', 'ë' => 'e',
'ì' => 'i', 'í' => 'i',
'î' => 'i', 'ï' => 'i',
'ð' => 'd', 'ñ' => 'n',
'ò' => 'o', 'ó' => 'o',
'ô' => 'o', 'õ' => 'o',
'ö' => 'o', 'ø' => 'o',
'ù' => 'u', 'ú' => 'u',
'û' => 'u', 'ü' => 'u',
'ý' => 'y', 'þ' => 'th',
'ÿ' => 'y', 'Ø' => 'O',
// Decompositions for Latin Extended-A
'Ā' => 'A', 'ā' => 'a',
'Ă' => 'A', 'ă' => 'a',
'Ą' => 'A', 'ą' => 'a',
'Ć' => 'C', 'ć' => 'c',
'Ĉ' => 'C', 'ĉ' => 'c',
'Ċ' => 'C', 'ċ' => 'c',
'Č' => 'C', 'č' => 'c',
'Ď' => 'D', 'ď' => 'd',
'Đ' => 'D', 'đ' => 'd',
'Ē' => 'E', 'ē' => 'e',
'Ĕ' => 'E', 'ĕ' => 'e',
'Ė' => 'E', 'ė' => 'e',
'Ę' => 'E', 'ę' => 'e',
'Ě' => 'E', 'ě' => 'e',
'Ĝ' => 'G', 'ĝ' => 'g',
'Ğ' => 'G', 'ğ' => 'g',
'Ġ' => 'G', 'ġ' => 'g',
'Ģ' => 'G', 'ģ' => 'g',
'Ĥ' => 'H', 'ĥ' => 'h',
'Ħ' => 'H', 'ħ' => 'h',
'Ĩ' => 'I', 'ĩ' => 'i',
'Ī' => 'I', 'ī' => 'i',
'Ĭ' => 'I', 'ĭ' => 'i',
'Į' => 'I', 'į' => 'i',
'İ' => 'I', 'ı' => 'i',
'IJ' => 'IJ', 'ij' => 'ij',
'Ĵ' => 'J', 'ĵ' => 'j',
'Ķ' => 'K', 'ķ' => 'k',
'ĸ' => 'k', 'Ĺ' => 'L',
'ĺ' => 'l', 'Ļ' => 'L',
'ļ' => 'l', 'Ľ' => 'L',
'ľ' => 'l', 'Ŀ' => 'L',
'ŀ' => 'l', 'Ł' => 'L',
'ł' => 'l', 'Ń' => 'N',
'ń' => 'n', 'Ņ' => 'N',
'ņ' => 'n', 'Ň' => 'N',
'ň' => 'n', 'ʼn' => 'n',
'Ŋ' => 'N', 'ŋ' => 'n',
'Ō' => 'O', 'ō' => 'o',
'Ŏ' => 'O', 'ŏ' => 'o',
'Ő' => 'O', 'ő' => 'o',
'Œ' => 'OE', 'œ' => 'oe',
'Ŕ' => 'R', 'ŕ' => 'r',
'Ŗ' => 'R', 'ŗ' => 'r',
'Ř' => 'R', 'ř' => 'r',
'Ś' => 'S', 'ś' => 's',
'Ŝ' => 'S', 'ŝ' => 's',
'Ş' => 'S', 'ş' => 's',
'Š' => 'S', 'š' => 's',
'Ţ' => 'T', 'ţ' => 't',
'Ť' => 'T', 'ť' => 't',
'Ŧ' => 'T', 'ŧ' => 't',
'Ũ' => 'U', 'ũ' => 'u',
'Ū' => 'U', 'ū' => 'u',
'Ŭ' => 'U', 'ŭ' => 'u',
'Ů' => 'U', 'ů' => 'u',
'Ű' => 'U', 'ű' => 'u',
'Ų' => 'U', 'ų' => 'u',
'Ŵ' => 'W', 'ŵ' => 'w',
'Ŷ' => 'Y', 'ŷ' => 'y',
'Ÿ' => 'Y', 'Ź' => 'Z',
'ź' => 'z', 'Ż' => 'Z',
'ż' => 'z', 'Ž' => 'Z',
'ž' => 'z', 'ſ' => 's',
// Decompositions for Latin Extended-B
'Ș' => 'S', 'ș' => 's',
'Ț' => 'T', 'ț' => 't',
// Euro Sign
'€' => 'E',
// GBP (Pound) Sign
'£' => '',
// Vowels with diacritic (Vietnamese)
// unmarked
'Ơ' => 'O', 'ơ' => 'o',
'Ư' => 'U', 'ư' => 'u',
// grave accent
'Ầ' => 'A', 'ầ' => 'a',
'Ằ' => 'A', 'ằ' => 'a',
'Ề' => 'E', 'ề' => 'e',
'Ồ' => 'O', 'ồ' => 'o',
'Ờ' => 'O', 'ờ' => 'o',
'Ừ' => 'U', 'ừ' => 'u',
'Ỳ' => 'Y', 'ỳ' => 'y',
// hook
'Ả' => 'A', 'ả' => 'a',
'Ẩ' => 'A', 'ẩ' => 'a',
'Ẳ' => 'A', 'ẳ' => 'a',
'Ẻ' => 'E', 'ẻ' => 'e',
'Ể' => 'E', 'ể' => 'e',
'Ỉ' => 'I', 'ỉ' => 'i',
'Ỏ' => 'O', 'ỏ' => 'o',
'Ổ' => 'O', 'ổ' => 'o',
'Ở' => 'O', 'ở' => 'o',
'Ủ' => 'U', 'ủ' => 'u',
'Ử' => 'U', 'ử' => 'u',
'Ỷ' => 'Y', 'ỷ' => 'y',
// tilde
'Ẫ' => 'A', 'ẫ' => 'a',
'Ẵ' => 'A', 'ẵ' => 'a',
'Ẽ' => 'E', 'ẽ' => 'e',
'Ễ' => 'E', 'ễ' => 'e',
'Ỗ' => 'O', 'ỗ' => 'o',
'Ỡ' => 'O', 'ỡ' => 'o',
'Ữ' => 'U', 'ữ' => 'u',
'Ỹ' => 'Y', 'ỹ' => 'y',
// acute accent
'Ấ' => 'A', 'ấ' => 'a',
'Ắ' => 'A', 'ắ' => 'a',
'Ế' => 'E', 'ế' => 'e',
'Ố' => 'O', 'ố' => 'o',
'Ớ' => 'O', 'ớ' => 'o',
'Ứ' => 'U', 'ứ' => 'u',
// dot below
'Ạ' => 'A', 'ạ' => 'a',
'Ậ' => 'A', 'ậ' => 'a',
'Ặ' => 'A', 'ặ' => 'a',
'Ẹ' => 'E', 'ẹ' => 'e',
'Ệ' => 'E', 'ệ' => 'e',
'Ị' => 'I', 'ị' => 'i',
'Ọ' => 'O', 'ọ' => 'o',
'Ộ' => 'O', 'ộ' => 'o',
'Ợ' => 'O', 'ợ' => 'o',
'Ụ' => 'U', 'ụ' => 'u',
'Ự' => 'U', 'ự' => 'u',
'Ỵ' => 'Y', 'ỵ' => 'y',
// Vowels with diacritic (Chinese, Hanyu Pinyin)
'ɑ' => 'a',
// macron
'Ǖ' => 'U', 'ǖ' => 'u',
// acute accent
'Ǘ' => 'U', 'ǘ' => 'u',
// caron
'Ǎ' => 'A', 'ǎ' => 'a',
'Ǐ' => 'I', 'ǐ' => 'i',
'Ǒ' => 'O', 'ǒ' => 'o',
'Ǔ' => 'U', 'ǔ' => 'u',
'Ǚ' => 'U', 'ǚ' => 'u',
// grave accent
'Ǜ' => 'U', 'ǜ' => 'u',
);
$string = strtr($string, $chars);
return $string;
}
}

View File

@@ -14,6 +14,10 @@ class BandcampBridge extends BridgeAbstract {
)
));
public function getIcon() {
return 'https://s4.bcbits.com/img/bc_favicon.ico';
}
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('No results for this query.');

View File

@@ -1,31 +1,46 @@
<?php
class BlaguesDeMerdeBridge extends BridgeAbstract {
const MAINTAINER = 'superbaillot.net';
const MAINTAINER = 'superbaillot.net, logmanoriginal';
const NAME = 'Blagues De Merde';
const URI = 'http://www.blaguesdemerde.fr/';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Blagues De Merde';
public function getIcon() {
return self::URI . 'assets/img/favicon.ico';
}
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request BDM.');
foreach($html->find('article.joke_contener') as $element) {
$item = array();
$temp = $element->find('a');
foreach($html->find('div.blague') as $element) {
$item = array();
$item['uri'] = static::URI . '#' . $element->id;
$item['author'] = $element->find('div[class="blague-footer"] p strong', 0)->plaintext;
// Let the title be everything up to the first <br>
$item['title'] = trim(explode("\n", $element->find('div.text', 0)->plaintext)[0]);
$item['content'] = strip_tags($element->find('div.text', 0));
// timestamp is part of:
// <p>Par <strong>{author}</strong> le {date} dans <strong>{category}</strong></p>
preg_match(
'/.+le(.+)dans.*/',
$element->find('div[class="blague-footer"]', 0)->plaintext,
$matches
);
$item['timestamp'] = strtotime($matches[1]);
$this->items[] = $item;
if(isset($temp[2])) {
$item['content'] = trim($element->find('div.joke_text_contener', 0)->innertext);
$uri = $temp[2]->href;
$item['uri'] = $uri;
$item['title'] = substr($uri, (strrpos($uri, '/') + 1));
$date = $element->find('li.bdm_date', 0)->innertext;
$time = mktime(0, 0, 0, substr($date, 3, 2), substr($date, 0, 2), substr($date, 6, 4));
$item['timestamp'] = $time;
$item['author'] = $element->find('li.bdm_pseudo', 0)->innertext;
$this->items[] = $item;
}
}
}
}

View File

@@ -31,6 +31,10 @@ class BloombergBridge extends BridgeAbstract
return parent::getName();
}
public function getIcon() {
return 'https://assets.bwbx.io/s3/javelin/public/hub/images/favicon-black-63fe5249d3.png';
}
public function collectData()
{
switch($this->queriedContext) {

View File

@@ -0,0 +1,86 @@
<?php
class BundesbankBridge extends BridgeAbstract {
const PARAM_LANG = 'lang';
const LANG_EN = 'en';
const LANG_DE = 'de';
const NAME = 'Bundesbank Bridge';
const URI = 'https://www.bundesbank.de/';
const DESCRIPTION = 'Returns the latest studies of the Bundesbank (Germany)';
const MAINTAINER = 'logmanoriginal';
const CACHE_TIMEOUT = 86400; // 24 hours
const PARAMETERS = array(
array(
self::PARAM_LANG => array(
'name' => 'Language',
'type' => 'list',
'required' => true,
'defaultValue' => self::LANG_DE,
'values' => array(
'English' => self::LANG_EN,
'Deutsch' => self::LANG_DE
)
)
)
);
public function getIcon() {
return self::URI . 'resource/crblob/1890/a7f48ee0ae35348748121770ba3ca009/mL/favicon-ico-data.ico';
}
public function getURI() {
switch($this->getInput(self::PARAM_LANG)) {
case self::LANG_EN: return self::URI . 'en/publications/reports/studies';
case self::LANG_DE: return self::URI . 'de/publikationen/berichte/studien';
}
return parent::getURI();
}
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('No response for ' . $this->getURI());
$html = defaultLinkTo($html, $this->getURI());
foreach($html->find('ul.resultlist li') as $study) {
$item = array();
$item['uri'] = $study->find('.teasable__link', 0)->href;
// Get title without child elements (i.e. subtitle)
$title = $study->find('.teasable__title div.h2', 0);
foreach($title->children as &$child) {
$child->outertext = '';
}
$item['title'] = $title->innertext;
// Add subtitle to the content if it exists
$item['content'] = '';
if($subtitle = $study->find('.teasable__subtitle', 0)) {
$item['content'] .= '<strong>' . $study->find('.teasable__subtitle', 0)->plaintext . '</strong>';
}
$item['content'] .= '<p>' . $study->find('.teasable__text', 0)->plaintext . '</p>';
$item['timestamp'] = strtotime($study->find('.teasable__date', 0)->plaintext);
// Downloads and older studies don't have images
if($study->find('.teasable__image', 0)) {
$item['enclosures'] = array(
$study->find('.teasable__image img', 0)->src
);
}
$this->items[] = $item;
}
}
}

View File

@@ -1,45 +0,0 @@
<?php
class CADBridge extends FeedExpander {
const MAINTAINER = 'nyutag';
const NAME = 'CAD Bridge';
const URI = 'http://www.cad-comic.com/';
const CACHE_TIMEOUT = 7200; //2h
const DESCRIPTION = 'Returns the newest articles.';
public function collectData(){
$this->collectExpandableDatas('http://cdn2.cad-comic.com/rss.xml', 10);
}
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
$item['content'] = $this->extractCADContent($item['uri']);
return $item;
}
private function extractCADContent($url) {
$html3 = getSimpleHTMLDOMCached($url);
// The request might fail due to missing https support or wrong URL
if($html3 == false)
return 'Daily comic not released yet';
$htmlpart = explode('/', $url);
switch ($htmlpart[3]) {
case 'cad':
preg_match_all('/http:\/\/cdn2\.cad-comic\.com\/comics\/cad-\S*png/', $html3, $url2);
break;
case 'sillies':
preg_match_all('/http:\/\/cdn2\.cad-comic\.com\/comics\/sillies-\S*gif/', $html3, $url2);
break;
default:
return 'Daily comic not released yet';
}
$img = implode($url2[0]);
$html3->clear();
unset($html3);
if ($img == '')
return 'Daily comic not released yet';
return '<img src="' . $img . '"/>';
}
}

View File

@@ -3,91 +3,107 @@ class CNETBridge extends BridgeAbstract {
const MAINTAINER = 'ORelio';
const NAME = 'CNET News';
const URI = 'http://www.cnet.com/';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = 'Returns the newest articles. <br /> You may specify a
topic found in some section URLs, else all topics are selected.';
const PARAMETERS = array( array(
'topic' => array(
'name' => 'Topic name'
const URI = 'https://www.cnet.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns the newest articles.';
const PARAMETERS = array(
array(
'topic' => array(
'name' => 'Topic',
'type' => 'list',
'values' => array(
'All articles' => '',
'Apple' => 'apple',
'Google' => 'google',
'Microsoft' => 'tags-microsoft',
'Computers' => 'topics-computers',
'Mobile' => 'topics-mobile',
'Sci-Tech' => 'topics-sci-tech',
'Security' => 'topics-security',
'Internet' => 'topics-internet',
'Tech Industry' => 'topics-tech-industry'
)
)
)
));
);
public function collectData(){
private function cleanArticle($article_html) {
$offset_p = strpos($article_html, '<p>');
$offset_figure = strpos($article_html, '<figure');
$offset = ($offset_figure < $offset_p ? $offset_figure : $offset_p);
$article_html = substr($article_html, $offset);
$article_html = str_replace('href="/', 'href="' . self::URI, $article_html);
$article_html = str_replace(' height="0"', '', $article_html);
$article_html = str_replace('<noscript>', '', $article_html);
$article_html = str_replace('</noscript>', '', $article_html);
$article_html = StripWithDelimiters($article_html, '<a class="clickToEnlarge', '</a>');
$article_html = stripWithDelimiters($article_html, '<span class="nowPlaying', '</span>');
$article_html = stripWithDelimiters($article_html, '<span class="duration', '</span>');
$article_html = stripWithDelimiters($article_html, '<script', '</script>');
$article_html = stripWithDelimiters($article_html, '<svg', '</svg>');
return $article_html;
}
function extractFromDelimiters($string, $start, $end){
if(strpos($string, $start) !== false) {
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
return $section_retrieved;
public function collectData() {
// Retrieve and check user input
$topic = str_replace('-', '/', $this->getInput('topic'));
if (!empty($topic) && (substr_count($topic, '/') > 1 || !ctype_alpha(str_replace('/', '', $topic))))
returnClientError('Invalid topic: ' . $topic);
// Retrieve webpage
$pageUrl = self::URI . (empty($topic) ? 'news/' : $topic . '/');
$html = getSimpleHTMLDOM($pageUrl)
or returnServerError('Could not request CNET: ' . $pageUrl);
// Process articles
foreach($html->find('div.assetBody, div.riverPost') as $element) {
if(count($this->items) >= 10) {
break;
}
return false;
}
$article_title = trim($element->find('h2, h3', 0)->plaintext);
$article_uri = self::URI . substr($element->find('a', 0)->href, 1);
$article_thumbnail = $element->parent()->find('img[src]', 0)->src;
$article_timestamp = strtotime($element->find('time.assetTime, div.timeAgo', 0)->plaintext);
$article_author = trim($element->find('a[rel=author], a.name', 0)->plaintext);
$article_content = '<p><b>' . trim($element->find('p.dek', 0)->plaintext) . '</b></p>';
function stripWithDelimiters($string, $start, $end){
while(strpos($string, $start) !== false) {
$section_to_remove = substr($string, strpos($string, $start));
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
$string = str_replace($section_to_remove, '', $string);
}
if (is_null($article_thumbnail))
$article_thumbnail = extractFromDelimiters($element->innertext, '<img src="', '"');
return $string;
}
if (!empty($article_title) && !empty($article_uri) && strpos($article_uri, self::URI . 'news/') !== false) {
function cleanArticle($article_html){
$article_html = '<p>' . substr($article_html, strpos($article_html, '<p>') + 3);
$article_html = stripWithDelimiters($article_html, '<span class="credit">', '</span>');
$article_html = stripWithDelimiters($article_html, '<script', '</script>');
$article_html = stripWithDelimiters($article_html, '<div class="shortcode related-links', '</div>');
$article_html = stripWithDelimiters($article_html, '<a class="clickToEnlarge">', '</a>');
return $article_html;
}
$article_html = getSimpleHTMLDOMCached($article_uri) or $article_html = null;
$pageUrl = self::URI . (empty($this->getInput('topic')) ? '' : 'topics/' . $this->getInput('topic') . '/');
$html = getSimpleHTMLDOM($pageUrl) or returnServerError('Could not request CNET: ' . $pageUrl);
$limit = 0;
if (!is_null($article_html)) {
foreach($html->find('div.assetBody') as $element) {
if($limit < 8) {
$article_title = trim($element->find('h2', 0)->plaintext);
$article_uri = self::URI . ($element->find('a', 0)->href);
$article_timestamp = strtotime($element->find('time.assetTime', 0)->plaintext);
$article_author = trim($element->find('a[rel=author]', 0)->plaintext);
if (empty($article_thumbnail))
$article_thumbnail = $article_html->find('div.originalImage', 0);
if (empty($article_thumbnail))
$article_thumbnail = $article_html->find('span.imageContainer', 0);
if (is_object($article_thumbnail))
$article_thumbnail = $article_thumbnail->find('img', 0)->src;
if(!empty($article_title) && !empty($article_uri) && strpos($article_uri, '/news/') !== false) {
$article_html = getSimpleHTMLDOM($article_uri)
or returnServerError('Could not request CNET: ' . $article_uri);
$article_content = trim(
cleanArticle(
$article_content .= trim(
$this->cleanArticle(
extractFromDelimiters(
$article_html,
'<div class="articleContent',
'<footer>'
$article_html, '<article', '<footer'
)
)
);
$item = array();
$item['uri'] = $article_uri;
$item['title'] = $article_title;
$item['author'] = $article_author;
$item['timestamp'] = $article_timestamp;
$item['content'] = $article_content;
$this->items[] = $item;
$limit++;
}
$item = array();
$item['uri'] = $article_uri;
$item['title'] = $article_title;
$item['author'] = $article_author;
$item['timestamp'] = $article_timestamp;
$item['enclosures'] = array($article_thumbnail);
$item['content'] = $article_content;
$this->items[] = $item;
}
}
}
public function getName(){
if(!is_null($this->getInput('topic'))) {
$topic = $this->getInput('topic');
return 'CNET News Bridge' . (empty($topic) ? '' : ' - ' . $topic);
}
return parent::getName();
}
}

View File

@@ -7,6 +7,9 @@ class ChristianDailyReporterBridge extends BridgeAbstract {
const DESCRIPTION = 'The Unofficial Christian Daily Reporter RSS';
// const CACHE_TIMEOUT = 86400; // 1 day
public function getIcon() {
return self::URI . 'images/cdrfavicon.png';
}
public function collectData() {
$uri = 'https://www.christiandailyreporter.com/';

View File

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

View File

@@ -26,12 +26,16 @@ class ContainerLinuxReleasesBridge extends BridgeAbstract {
]
];
public function getReleaseFeed($jsonUrl) {
private function getReleaseFeed($jsonUrl) {
$json = getContents($jsonUrl)
or returnServerError('Could not request Core OS Website.');
return json_decode($json, true);
}
public function getIcon() {
return 'https://coreos.com/assets/ico/favicon.png';
}
public function collectData() {
$data = $this->getReleaseFeed($this->getJsonUri());

227
bridges/CrewbayBridge.php Normal file
View File

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

View File

@@ -48,6 +48,10 @@ class DailymotionBridge extends BridgeAbstract {
return $metadata;
}
public function getIcon() {
return 'https://static1-ssl.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vf806ca4ed0deed812';
}
public function collectData(){
$html = '';
$limit = 5;

View File

@@ -1,7 +1,7 @@
<?php
class DanbooruBridge extends BridgeAbstract {
const MAINTAINER = 'mitsukarenai';
const MAINTAINER = 'mitsukarenai, logmanoriginal';
const NAME = 'Danbooru';
const URI = 'http://donmai.us/';
const CACHE_TIMEOUT = 1800; // 30min
@@ -57,11 +57,80 @@ class DanbooruBridge extends BridgeAbstract {
}
public function collectData(){
$html = getSimpleHTMLDOM($this->getFullURI())
$content = getContents($this->getFullURI())
or returnServerError('Could not request ' . $this->getName());
$html = Fix_Simple_Html_Dom::str_get_html($content);
foreach($html->find(static::PATHTODATA) as $element) {
$this->items[] = $this->getItemFromElement($element);
}
}
}
/**
* This class is a monkey patch to 'extend' simplehtmldom to recognize <source>
* tags (HTML5) as self closing tag. This patch should be removed once
* simplehtmldom was fixed. This seems to be a issue with more tags:
* https://sourceforge.net/p/simplehtmldom/bugs/83/
*
* The tag itself is valid according to Mozilla:
*
* The HTML <picture> element serves as a container for zero or more <source>
* elements and one <img> element to provide versions of an image for different
* display device scenarios. The browser will consider each of the child <source>
* elements and select one corresponding to the best match found; if no matches
* are found among the <source> elements, the file specified by the <img>
* element's src attribute is selected. The selected image is then presented in
* the space occupied by the <img> element.
*
* -- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture
*
* Notice: This class uses parts of the original simplehtmldom, adjusted to pass
* the guidelines of RSS-Bridge (formatting)
*/
final class Fix_Simple_Html_Dom extends simple_html_dom {
/* copy from simple_html_dom, added 'source' at the end */
protected $self_closing_tags = array(
'img' => 1,
'br' => 1,
'input' => 1,
'meta' => 1,
'link' => 1,
'hr' => 1,
'base' => 1,
'embed' => 1,
'spacer' => 1,
'source' => 1
);
/* copy from simplehtmldom, changed 'simple_html_dom' to 'Fix_Simple_Html_Dom' */
public static function str_get_html($str,
$lowercase = true,
$forceTagsClosed = true,
$target_charset = DEFAULT_TARGET_CHARSET,
$stripRN = true,
$defaultBRText = DEFAULT_BR_TEXT,
$defaultSpanText = DEFAULT_SPAN_TEXT)
{
$dom = new Fix_Simple_Html_Dom(null,
$lowercase,
$forceTagsClosed,
$target_charset,
$stripRN,
$defaultBRText,
$defaultSpanText);
if (empty($str) || strlen($str) > MAX_FILE_SIZE) {
$dom->clear();
return false;
}
$dom->load($str, $lowercase, $stripRN);
return $dom;
}
}

View File

@@ -3,7 +3,7 @@ class DauphineLibereBridge extends FeedExpander {
const MAINTAINER = 'qwertygc';
const NAME = 'Dauphine Bridge';
const URI = 'http://www.ledauphine.com/';
const URI = 'https://www.ledauphine.com/';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Returns the newest articles.';
@@ -49,8 +49,9 @@ class DauphineLibereBridge extends FeedExpander {
private function extractContent($url){
$html2 = getSimpleHTMLDOMCached($url);
$text = $html2->find('div.column', 0)->innertext;
$text = preg_replace('@<script[^>]*?>.*?</script>@si', '', $text);
return $text;
foreach ($html2->find('.noprint, link, script, iframe, .shareTool, .contentInfo') as $remove) {
$remove->outertext = '';
}
return $html2->find('div.content', 0)->innertext;
}
}

File diff suppressed because it is too large Load Diff

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;
}
}
}

239
bridges/DesoutterBridge.php Normal file
View File

@@ -0,0 +1,239 @@
<?php
class DesoutterBridge extends BridgeAbstract {
const CATEGORY_NEWS = 'News & Events';
const CATEGORY_INDUSTRY = 'Industry 4.0 News';
const NAME = 'Desoutter Bridge';
const URI = 'https://www.desouttertools.com';
const DESCRIPTION = 'Returns feeds for news from Desoutter';
const MAINTAINER = 'logmanoriginal';
const CACHE_TIMEOUT = 86400; // 24 hours
const PARAMETERS = array(
self::CATEGORY_NEWS => array(
'news_lang' => array(
'name' => 'Language',
'type' => 'list',
'required' => true,
'title' => 'Select your language',
'defaultValue' => 'Corporate',
'values' => array(
'Corporate'
=> 'https://www.desouttertools.com/about-desoutter/news-events',
'Česko'
=> 'https://www.desouttertools.cz/o-desoutter/aktuality-udalsoti',
'Deutschland'
=> 'https://www.desoutter.de/ueber-desoutter/news-events',
'España'
=> 'https://www.desouttertools.es/sobre-desoutter/noticias-eventos',
'México'
=> 'https://www.desouttertools.mx/acerca-desoutter/noticias-eventos',
'France'
=> 'https://www.desouttertools.fr/a-propos-de-desoutter/actualites-evenements',
'Magyarország'
=> 'https://www.desouttertools.hu/a-desoutter-vallalatrol/hirek-esemenyek',
'Italia'
=> 'https://www.desouttertools.it/su-desoutter/news-eventi',
'日本'
=> 'https://www.desouttertools.jp/desotanituite/niyusu-ibento',
'대한민국'
=> 'https://www.desouttertools.co.kr/desoteoe-daehaeseo/nyuseu-mic-ibenteu',
'Polska'
=> 'https://www.desouttertools.pl/o-desoutter/aktualnosci-wydarzenia',
'Brasil'
=> 'https://www.desouttertools.com.br/sobre-desoutter/noti%C2%ADcias-eventos',
'Portugal'
=> 'https://www.desouttertools.pt/sobre-desoutter/notIcias-eventos',
'România'
=> 'https://www.desouttertools.ro/despre-desoutter/noutati-evenimente',
'Российская Федерация'
=> 'https://www.desouttertools.com.ru/o-desoutter/novosti-mieropriiatiia',
'Slovensko'
=> 'https://www.desouttertools.sk/o-spolocnosti-desoutter/novinky-udalosti',
'Slovenija'
=> 'https://www.desouttertools.si/o-druzbi-desoutter/novice-dogodki',
'Sverige'
=> 'https://www.desouttertools.se/om-desoutter/nyheter-evenemang',
'Türkiye'
=> 'https://www.desoutter.com.tr/desoutter-hakkinda/haberler-etkinlikler',
'中国'
=> 'https://www.desouttertools.com.cn/guan-yu-ma-tou/xin-wen-he-huo-dong',
)
),
),
self::CATEGORY_INDUSTRY => array(
'industry_lang' => array(
'name' => 'Language',
'type' => 'list',
'required' => true,
'title' => 'Select your language',
'defaultValue' => 'Corporate',
'values' => array(
'Corporate'
=> 'https://www.desouttertools.com/industry-4-0/news',
'Česko'
=> 'https://www.desouttertools.cz/prumysl-4-0/novinky',
'Deutschland'
=> 'https://www.desoutter.de/industrie-4-0/news',
'España'
=> 'https://www.desouttertools.es/industria-4-0/noticias',
'México'
=> 'https://www.desouttertools.mx/industria-4-0/noticias',
'France'
=> 'https://www.desouttertools.fr/industrie-4-0/actualites',
'Magyarország'
=> 'https://www.desouttertools.hu/industry-4-0/hirek',
'Italia'
=> 'https://www.desouttertools.it/industry-4-0/news',
'日本'
=> 'https://www.desouttertools.jp/industry-4-0/news',
'대한민국'
=> 'https://www.desouttertools.co.kr/industry-4-0/news',
'Polska'
=> 'https://www.desouttertools.pl/przemysl-4-0/wiadomosci',
'Brasil'
=> 'https://www.desouttertools.com.br/industria-4-0/noticias',
'Portugal'
=> 'https://www.desouttertools.pt/industria-4-0/noticias',
'România'
=> 'https://www.desouttertools.ro/industry-4-0/noutati',
'Российская Федерация'
=> 'https://www.desouttertools.com.ru/industry-4-0/news',
'Slovensko'
=> 'https://www.desouttertools.sk/priemysel-4-0/novinky',
'Slovenija'
=> 'https://www.desouttertools.si/industrija-4-0/novice',
'Sverige'
=> 'https://www.desouttertools.se/industri-4-0/nyheter',
'Türkiye'
=> 'https://www.desoutter.com.tr/endustri-4-0/haberler',
'中国'
=> 'https://www.desouttertools.com.cn/industry-4-0/news',
)
),
),
'global' => array(
'full' => array(
'name' => 'Load full articles',
'type' => 'checkbox',
'required' => false,
'title' => 'Enable to load the full article for each item'
)
)
);
private $title;
public function getURI() {
switch($this->queriedContext) {
case self::CATEGORY_NEWS:
return $this->getInput('news_lang') ?: parent::getURI();
case self::CATEGORY_INDUSTRY:
return $this->getInput('industry_lang') ?: parent::getURI();
}
return parent::getURI();
}
public function getName() {
return isset($this->title) ? $this->title . ' - ' . parent::getName() : parent::getName();
}
public function collectData() {
// Uncomment to generate list of languages automtically (dev mode)
/*
switch($this->queriedContext) {
case self::CATEGORY_NEWS:
$this->extractNewsLanguages(); die;
case self::CATEGORY_INDUSTRY:
$this->extractIndustryLanguages(); die;
}
*/
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request ' . $this->getURI());
$html = defaultLinkTo($html, $this->getURI());
$this->title = html_entity_decode($html->find('title', 0)->plaintext, ENT_QUOTES);
foreach($html->find('article') as $article) {
$item = array();
$item['uri'] = $article->find('[itemprop="name"]', 0)->href;
$item['title'] = $article->find('[itemprop="name"]', 0)->title;
if($this->getInput('full')) {
$item['content'] = $this->getFullNewsArticle($item['uri']);
} else {
$item['content'] = $article->find('[itemprop="description"]', 0)->plaintext;
}
$this->items[] = $item;
}
}
private function getFullNewsArticle($uri) {
$html = getSimpleHTMLDOMCached($uri)
or returnServerError('Unable to load full article!');
$html = defaultLinkTo($html, $this->getURI());
return $html->find('section.article', 0);
}
/**
* Generates a HTML page with a PHP formatted array of languages,
* pointing to the corresponding news pages. Implementation is based
* on the 'Corporate' site.
* @return void
*/
private function extractNewsLanguages() {
$html = getSimpleHTMLDOMCached('https://www.desouttertools.com/about-desoutter/news-events')
or returnServerError('Error loading news!');
$html = defaultLinkTo($html, static::URI);
$items = $html->find('ul[class="dropdown-menu"] li');
$list = "\t'Corporate'\n\t=> 'https://www.desouttertools.com/about-desoutter/news-events',\n";
foreach($items as $item) {
$lang = trim($item->plaintext);
$uri = $item->find('a', 0)->href;
$list .= "\t'{$lang}'\n\t=> '{$uri}',\n";
}
echo $list;
}
/**
* Generates a HTML page with a PHP formatted array of languages,
* pointing to the corresponding news pages. Implementation is based
* on the 'Corporate' site.
* @return void
*/
private function extractIndustryLanguages() {
$html = getSimpleHTMLDOMCached('https://www.desouttertools.com/industry-4-0/news')
or returnServerError('Error loading news!');
$html = defaultLinkTo($html, static::URI);
$items = $html->find('ul[class="dropdown-menu"] li');
$list = "\t'Corporate'\n\t=> 'https://www.desouttertools.com/industry-4-0/news',\n";
foreach($items as $item) {
$lang = trim($item->plaintext);
$uri = $item->find('a', 0)->href;
$list .= "\t'{$lang}'\n\t=> '{$uri}',\n";
}
echo $list;
}
}

103
bridges/DevToBridge.php Normal file
View File

@@ -0,0 +1,103 @@
<?php
class DevToBridge extends BridgeAbstract {
const CONTEXT_BY_TAG = 'By tag';
const NAME = 'dev.to Bridge';
const URI = 'https://dev.to';
const DESCRIPTION = 'Returns feeds for tags';
const MAINTAINER = 'logmanoriginal';
const CACHE_TIMEOUT = 10800; // 15 min.
const PARAMETERS = array(
self::CONTEXT_BY_TAG => array(
'tag' => array(
'name' => 'Tag',
'type' => 'text',
'required' => true,
'title' => 'Insert your tag',
'exampleValue' => 'python'
),
'full' => array(
'name' => 'Full article',
'type' => 'checkbox',
'required' => false,
'title' => 'Enable to receive the full article for each item'
)
)
);
public function getURI() {
switch($this->queriedContext) {
case self::CONTEXT_BY_TAG:
if($tag = $this->getInput('tag')) {
return static::URI . '/t/' . urlencode($tag);
}
break;
}
return parent::getURI();
}
public function getIcon() {
return 'https://practicaldev-herokuapp-com.freetls.fastly.net/assets/
apple-icon-5c6fa9f2bce280428589c6195b7f1924206a53b782b371cfe2d02da932c8c173.png';
}
public function collectData() {
$html = getSimpleHTMLDOMCached($this->getURI())
or returnServerError('Could not request ' . $this->getURI());
$html = defaultLinkTo($html, static::URI);
$articles = $html->find('div[class="single-article"]')
or returnServerError('Could not find articles!');
foreach($articles as $article) {
if($article->find('[class*="cta"]', 0)) { // Skip ads
continue;
}
$item = array();
$item['uri'] = $article->find('a[id*=article-link]', 0)->href;
$item['title'] = $article->find('h3', 0)->plaintext;
// i.e. "Charlie Harrington・Sep 21"
$item['timestamp'] = strtotime(explode('・', $article->find('h4 a', 0)->plaintext, 2)[1]);
$item['author'] = explode('・', $article->find('h4 a', 0)->plaintext, 2)[0];
// Profile image
$item['enclosures'] = array($article->find('img', 0)->src);
if($this->getInput('full')) {
$fullArticle = $this->getFullArticle($item['uri']);
$item['content'] = <<<EOD
<img src="{$item['enclosures'][0]}" alt="{$item['author']}">
<p>{$fullArticle}</p>
EOD;
} else {
$item['content'] = <<<EOD
<img src="{$item['enclosures'][0]}" alt="{$item['author']}">
<p>{$item['title']}</p>
EOD;
}
$item['categories'] = array_map(function($e){ return $e->plaintext; }, $article->find('div.tags span.tag'));
$this->items[] = $item;
}
}
private function getFullArticle($url) {
$html = getSimpleHTMLDOMCached($url)
or returnServerError('Unable to load article from "' . $url . '"!');
$html = defaultLinkTo($html, static::URI);
return $html->find('[id="article-body"]', 0);
}
}

View File

@@ -75,6 +75,10 @@ class DiceBridge extends BridgeAbstract {
),
));
public function getIcon() {
return 'https://assets.dice.com/techpro/img/favicons/favicon.ico';
}
public function collectData() {
$uri = 'https://www.dice.com/jobs/advancedResult.html';
$uri .= '?for_one=' . urlencode($this->getInput('for_one'));

View File

@@ -3,23 +3,23 @@ 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';
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request Dilbert: ' . $this->getURI());
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request Dilbert: ' . self::URI);
foreach($html->find('section.comic-item') as $element) {
$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;
}
@@ -81,7 +85,7 @@ class DiscogsBridge extends BridgeAbstract {
. $this->getInput('username_folder')
. '/collection/folders/'
. $this->getInput('folderid')
.'/releases?sort=added&sort_order=desc')
. '/releases?sort=added&sort_order=desc')
or returnServerError('Unable to query discogs !');
$jsonData = json_decode($data, true)['releases'];
}

View File

@@ -7,6 +7,11 @@ class DribbbleBridge extends BridgeAbstract {
const CACHE_TIMEOUT = 1800;
const DESCRIPTION = 'Returns the newest popular shots from Dribbble.';
public function getIcon() {
return 'https://cdn.dribbble.com/assets/
favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
}
public function collectData(){
$html = getSimpleHTMLDOM(self::URI . '/shots')
or returnServerError('Error while downloading the website content');

View File

@@ -94,17 +94,20 @@ class ETTVBridge extends BridgeAbstract {
)
));
protected $results_link;
public function collectData(){
// No control on inputs, because all have defaultValue set
// No control on inputs, because all defaultValue are set
$query_str = 'torrents-search.php';
$query_str .= '?search=' . urlencode('+'.str_replace(' ', ' +', $this->getInput('query')));
$query_str .= '?search=' . urlencode('+' . str_replace(' ', ' +', $this->getInput('query')));
$query_str .= '&cat=' . $this->getInput('cat');
$query_str .= 'incldead&=' . $this->getInput('status');
$query_str .= '&incldead=' . $this->getInput('status');
$query_str .= '&lang=' . $this->getInput('lang');
$query_str .= '&sort=id&order=desc';
// Get results page
$html = getSimpleHTMLDOM(self::URI . $query_str)
$this->results_link = self::URI . $query_str;
$html = getSimpleHTMLDOM($this->results_link)
or returnServerError('Could not request ' . $this->getName());
// Loop on each entry
@@ -125,7 +128,7 @@ class ETTVBridge extends BridgeAbstract {
$item = array();
$item['author'] = $details->children(6)->children(1)->plaintext;
$item['title'] = $entry->title;
$item['uri'] = $dllinks->children(0)->children(0)->children(0)->href;
$item['uri'] = $link;
$item['timestamp'] = strtotime($details->children(7)->children(1)->plaintext);
$item['content'] = '';
$item['content'] .= '<br/><b>Name: </b>' . $details->children(0)->children(1)->innertext;
@@ -139,4 +142,20 @@ class ETTVBridge extends BridgeAbstract {
$this->items[] = $item;
}
}
public function getName(){
if($this->getInput('query')) {
return '[' . self::NAME . '] ' . $this->getInput('query');
}
return self::NAME;
}
public function getURI(){
if(isset($this->results_link) && !empty($this->results_link)) {
return $this->results_link;
}
return self::URI;
}
}

View File

@@ -6,20 +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';
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

@@ -58,7 +58,7 @@ class ElloBridge extends BridgeAbstract {
}
public function findText($path) {
private function findText($path) {
foreach($path as $summaryElement) {
@@ -72,7 +72,7 @@ class ElloBridge extends BridgeAbstract {
}
public function getPostContent($path) {
private function getPostContent($path) {
$content = '';
foreach($path as $summaryElement) {
@@ -93,7 +93,7 @@ class ElloBridge extends BridgeAbstract {
}
public function getEnclosures($post, $postData) {
private function getEnclosures($post, $postData) {
$assets = [];
foreach($post->links->assets as $asset) {
@@ -109,7 +109,7 @@ class ElloBridge extends BridgeAbstract {
}
public function getUsername($post, $postData) {
private function getUsername($post, $postData) {
foreach($postData->linked->users as $user) {
if($user->id == $post->links->author->id) {
@@ -119,9 +119,9 @@ class ElloBridge extends BridgeAbstract {
}
public function getAPIKey() {
private function getAPIKey() {
$cache = Cache::create('FileCache');
$cache->setPath(CACHE_DIR);
$cache->setPath(PATH_CACHE);
$cache->setParameters(['key']);
$key = $cache->loadData();
@@ -143,5 +143,4 @@ class ElloBridge extends BridgeAbstract {
return parent::getName();
}
}

View File

@@ -57,6 +57,10 @@ class ElsevierBridge extends BridgeAbstract {
return '';
}
public function getIcon() {
return 'https://cdn.elsevier.io/verona/includes/favicons/favicon-32x32.png';
}
public function collectData(){
$uri = self::URI . $this->getInput('j') . '/recent-articles/';
$html = getSimpleHTMLDOM($uri)

View File

@@ -7,19 +7,9 @@ class EstCeQuonMetEnProdBridge extends BridgeAbstract {
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'Should we put a website in production today? (French)';
public function collectData(){
function extractFromDelimiters($string, $start, $end){
if(strpos($string, $start) !== false) {
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
return $section_retrieved;
}
return false;
}
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request EstCeQuonMetEnProd: ' . $this->getURI());
public function collectData() {
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request EstCeQuonMetEnProd: ' . self::URI);
$item = array();
$item['uri'] = $this->getURI() . '#' . date('Y-m-d');
@@ -28,8 +18,8 @@ class EstCeQuonMetEnProdBridge extends BridgeAbstract {
$item['timestamp'] = strtotime('today midnight');
$item['content'] = str_replace(
'src="/',
'src="' . $this->getURI(),
trim(extractFromDelimiters($html->outertext, '<body role="document">', '<br /><br />'))
'src="' . self::URI,
trim(extractFromDelimiters($html->outertext, '<body role="document">', '<div id="share'))
);
$this->items[] = $item;

View File

@@ -17,7 +17,7 @@ class EtsyBridge extends BridgeAbstract {
'queryextension' => array(
'name' => 'Query extension',
'type' => 'text',
'requied' => false,
'required' => false,
'title' => 'Insert additional query parts here
(anything after ?search=<your search query>)',
'exampleValue' => '&explicit=1&locationQuery=2921044'
@@ -25,9 +25,9 @@ class EtsyBridge extends BridgeAbstract {
'showimage' => array(
'name' => 'Show image in content',
'type' => 'checkbox',
'requrired' => false,
'required' => false,
'title' => 'Activate to show the image in the content',
'defaultValue' => false
'defaultValue' => 'checked'
)
)
);
@@ -36,26 +36,27 @@ class EtsyBridge extends BridgeAbstract {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Failed to receive ' . $this->getURI());
$results = $html->find('div.block-grid-item');
$results = $html->find('li.block-grid-item');
foreach($results as $result) {
// Skip banner cards (ads for categories)
if($result->find('a.banner-card'))
if($result->find('span.ad-indicator'))
continue;
$item = array();
$item['title'] = $result->find('a', 0)->title;
$item['uri'] = $result->find('a', 0)->href;
$item['author'] = $result->find('div.card-shop-name', 0)->plaintext;
$item['author'] = $result->find('p.text-gray-lighter', 0)->plaintext;
$item['content'] = '<p>'
. $result->find('div.card-price', 0)->plaintext
. $result->find('span.currency-value', 0)->plaintext . ' '
. $result->find('span.currency-symbol', 0)->plaintext
. '</p><p>'
. $result->find('div.card-title', 0)->plaintext
. $result->find('a', 0)->title
. '</p>';
$image = $result->find('img.placeholder', 0)->src;
$image = $result->find('img.display-block', 0)->src;
if($this->getInput('showimage')) {
$item['content'] .= '<img src="' . $image . '">';

View File

@@ -0,0 +1,103 @@
<?php
class ExtremeDownloadBridge extends BridgeAbstract {
const NAME = 'Extreme Download';
const URI = 'https://ww1.extreme-d0wn.com/';
const DESCRIPTION = 'Suivi de série sur Extreme Download';
const MAINTAINER = 'sysadminstory';
const PARAMETERS = array(
'Suivre la publication des épisodes d\'une série en cours de diffusion' => array(
'url' => array(
'name' => 'URL de la série',
'type' => 'text',
'required' => true,
'title' => 'URL d\'une série sans le https://ww1.extreme-d0wn.com/',
'exampleValue' => 'series-hd/hd-series-vostfr/46631-halt-and-catch-fire-saison-04-vostfr-hdtv-720p.html'),
'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',
'Téléchargement' => 'download',
'Streaming' => 'streaming'
)
)
)
);
public function collectData(){
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
or returnServerError('Could not request Extreme Download.');
$filter = $this->getInput('filter');
$typesText = array(
'download' => 'Téléchargement',
'streaming' => 'Streaming'
);
// Get the TV show title
$this->showTitle = trim($html->find('span[id=news-title]', 0)->plaintext);
$list = $html->find('div[class=prez_7]');
foreach($list as $element) {
$add = false;
// Link type is needed is needed to generate an unique link
$type = $this->findLinkType($element);
if($filter == 'both') {
$add = true;
} else {
if($type == $filter) {
$add = true;
}
}
if($add == true) {
$item = array();
// Get the element name
$title = $element->plaintext;
// Get thee element links
$links = $element->next_sibling()->innertext;
$item['content'] = $links;
$item['title'] = $this->showTitle . ' ' . $title . ' - ' . $typesText[$type];
// As RSS Bridge use the URI as GUID they need to be unique : adding a md5 hash of the title element
// should geneerate unique URI to prevent confusion for RSS readers
$item['uri'] = self::URI . $this->getInput('url') . '#' . hash('md5', $item['title']);
$this->items[] = $item;
}
}
}
public function getName(){
switch($this->queriedContext) {
case 'Suivre la publication des épisodes d\'une série en cours de diffusion':
return $this->showTitle . ' - ' . self::NAME;
break;
default:
return self::NAME;
}
}
private function findLinkType($element)
{
$return = '';
// Walk through all elements in the reverse order until finding one with class 'presz_2'
while($element->class != 'prez_2') {
$element = $element->prev_sibling();
}
$text = html_entity_decode($element->plaintext);
// Regarding the text of the element, return the according link type
if(stristr($text, 'téléchargement') != false) {
$return = 'download';
} else if(stristr($text, 'streaming') != false) {
$return = 'streaming';
}
return $return;
}
}

View File

@@ -15,24 +15,18 @@ class FB2Bridge extends BridgeAbstract {
)
));
public function getIcon() {
return 'https://static.xx.fbcdn.net/rsrc.php/yo/r/iRmz9lCMBD2.ico';
}
public function collectData(){
function extractFromDelimiters($string, $start, $end){
if(strpos($string, $start) !== false) {
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
return $section_retrieved;
}
return false;
}
//Utility function for cleaning a Facebook link
$unescape_fb_link = function($matches){
if(is_array($matches) && count($matches) > 1) {
$link = $matches[1];
if(strpos($link, '/') === 0)
$link = self::URI . $link . '"';
$link = self::URI . substr($link, 1);
if(strpos($link, 'facebook.com/l.php?u=') !== false)
$link = urldecode(extractFromDelimiters($link, 'facebook.com/l.php?u=', '&'));
return ' href="' . $link . '"';
@@ -75,14 +69,14 @@ class FB2Bridge extends BridgeAbstract {
if($this->getInput('u') !== null) {
$page = 'https://touch.facebook.com/' . $this->getInput('u');
$cookies = $this->getCookies($page);
$pageID = $this->getPageID($page, $cookies);
$pageInfo = $this->getPageInfos($page, $cookies);
if($pageID === null) {
if($pageInfo['userId'] === null) {
echo <<<EOD
Unable to get the page id. You should consider getting the ID by hand, then importing it into FB2Bridge
EOD;
die();
} elseif($pageID == -1) {
} elseif($pageInfo['userId'] == -1) {
echo <<<EOD
This page is not accessible without being logged in.
EOD;
@@ -91,24 +85,31 @@ EOD;
}
//Build the string for the first request
$requestString = 'https://touch.facebook.com/pages_reaction_units/more/?page_id='
. $pageID
. '&cursor={"card_id"%3A"videos"%2C"has_next_page"%3Atrue}&surface=mobile_page_home&unit_count=8';
$requestString = 'https://touch.facebook.com/page_content_list_view/more/?page_id='
. $pageInfo['userId']
. '&start_cursor=1&num_to_fetch=105&surface_type=timeline';
$fileContent = getContents($requestString);
$articleIndex = 0;
$maxArticle = 3;
$html = $this->buildContent($fileContent);
$author = $this->getInput('u');
$author = $pageInfo['username'];
foreach($html->find('article') as $content) {
$item = array();
//echo $content; die();
preg_match('/publish_time\\\":([0-9]+),/', $content->getAttribute('data-store', 0), $match);
if(isset($match[1]))
$timestamp = $match[1];
else
$timestamp = 0;
$item['uri'] = 'http://touch.facebook.com'
. $content->find("div[class='_52jc _5qc4 _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href');
$item['uri'] = html_entity_decode('http://touch.facebook.com'
. $content->find("div[class='_52jc _5qc4 _78cz _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href'), ENT_QUOTES);
//Decode images
$imagecleaned = preg_replace_callback('/<i [^>]* style="[^"]*url\(\'(.*?)\'\).*?><\/i>/m', function ($matches) {
return "<img src='" . str_replace(['\\3a ', '\\3d ', '\\26 '], [':', '=', '&'], $matches[1]) . "' />";
}, $content);
$content = str_get_html($imagecleaned);
if($content->find('header', 0) !== null) {
$content->find('header', 0)->innertext = '';
@@ -118,8 +119,13 @@ EOD;
$content->find('footer', 0)->innertext = '';
}
// Replace emoticon images by their textual representation (part of the span)
foreach($content->find('span[title*="emoticon"]') as $emoticon) {
$emoticon->innertext = $emoticon->find('span[aria-hidden="true"]', 0)->innertext;
}
//Remove html nodes, keep only img, links, basic formatting
$content = strip_tags($content, '<a><img><i><u><br><p>');
$content = strip_tags($content, '<a><img><i><u><br><p><h3><h4><section>');
//Adapt link hrefs: convert relative links into absolute links and bypass external link redirection
$content = preg_replace_callback('/ href=\"([^"]+)\"/i', $unescape_fb_link, $content);
@@ -132,7 +138,6 @@ EOD;
'ajaxify',
'tabindex',
'class',
'style',
'data-[^=]*',
'aria-[^=]*',
'role',
@@ -145,7 +150,36 @@ EOD;
// "<i><u>smile emoticon</u></i>" back to ASCII emoticons eg ":)"
$content = preg_replace_callback('/<i><u>([^ <>]+) ([^<>]+)<\/u><\/i>/i', $unescape_fb_emote, $content);
$item['content'] = $content;
//Remove the "...Plus" tag
$content = preg_replace(
'/… (<span>|)<a href="https:\/\/www\.facebook\.com\/story\.php\?story_fbid=.*?<\/a>/m',
'', $content, 1);
//Remove tracking images
$content = preg_replace('/<img src=\'.*?safe_image\.php.*?\' \/>/m', '', $content);
//Remove the double section tags
$content = str_replace(['<section><section>', '</section></section>'], ['<section>', '</section>'], $content);
//Move the section tag link upper, if it is down
$content = str_get_html($content);
$sectionContent = $content->find('section', 0);
if($sectionContent != null) {
$sectionLink = $sectionContent->nextSibling();
if($sectionLink != null) {
$fullLink = '<a href="' . $sectionLink->getAttribute('href') . '">' . $sectionContent->innertext . '</a>';
$sectionContent->innertext = $fullLink;
}
}
//Move the href tag upper if it is inside the section
foreach($content->find('section > a') as $sectionToFix) {
$sectionLink = $sectionToFix->getAttribute('href');
$section = $sectionToFix->parent();
$section->outertext = '<a href="' . $sectionLink . '">' . $section . '</a>';
}
$item['content'] = html_entity_decode($content, ENT_QUOTES);
$title = $author;
if (strlen($title) > 24)
@@ -154,48 +188,14 @@ EOD;
if (strlen($title) > 64)
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
$item['title'] = $title;
$item['author'] = $author;
$item['title'] = html_entity_decode($title, ENT_QUOTES);
$item['author'] = html_entity_decode($author, ENT_QUOTES);
$item['timestamp'] = html_entity_decode($timestamp, ENT_QUOTES);
array_push($this->items, $item);
if($item['timestamp'] != 0)
array_push($this->items, $item);
}
}
// Currently not used. Is used to get more than only 3 elements, as they appear on another page.
private function computeNextLink($string, $pageID){
$regex = implode(
'',
array(
'/timeline_unit',
"\\\\\\\\u00253A1",
"\\\\\\\\u00253A([0-9]*)",
"\\\\\\\\u00253A([0-9]*)",
"\\\\\\\\u00253A([0-9]*)",
"\\\\\\\\u00253A([0-9]*)/"
)
);
preg_match($regex, $string, $result);
return implode(
'',
array(
'https://touch.facebook.com/pages_reaction_units/more/?page_id=',
$pageID,
'&cursor=%7B%22timeline_cursor%22%3A%22timeline_unit%3A1%3A',
$result[1],
'%3A',
$result[2],
'%3A',
$result[3],
'%3A',
$result[4],
'%22%2C%22timeline_section_cursor%22%3A%7B%7D%2C%22',
'has_next_page%22%3Atrue%7D&surface=mobile_page_home&unit_count=3'
)
);
}
//Builds the HTML from the encoded JS that Facebook provides.
@@ -204,9 +204,13 @@ EOD;
// /div>","replaceifexists
$regex = '/\\"html\\":(\".+\/div>"),"replace/';
preg_match($regex, $pageContent, $result);
return str_get_html(html_entity_decode(json_decode($result[1])));
}
$htmlContent = json_decode($result[1]);
$htmlContent = preg_replace('/(?<!style)="(.*?)"/', '=\'$1\'', $htmlContent);
$htmlContent = html_entity_decode($htmlContent, ENT_QUOTES, 'UTF-8');
return str_get_html($htmlContent);
}
//Builds the cookie from the page, as Facebook sometimes refuses to give
//the page if no cookie is provided.
@@ -234,8 +238,8 @@ EOD;
return substr($cookies, 1);
}
//Get the page ID from the Facebook page.
private function getPageID($page, $cookies){
//Get the page ID and username from the Facebook page.
private function getPageInfos($page, $cookies){
$context = stream_context_create(array(
'http' => array(
@@ -251,19 +255,28 @@ EOD;
return -1;
}
//Get the username
$usernameRegex = '/data-nt=\"FB:TEXT4\">(.*?)<\/div>/m';
preg_match($usernameRegex, $pageContent, $usernameMatches);
if(count($usernameMatches) > 0) {
$username = strip_tags($usernameMatches[1]);
} else {
$username = $this->getInput('u');
}
//Get the page ID if we don't have a captcha
$regex = '/page_id=([0-9]*)&/';
preg_match($regex, $pageContent, $matches);
if(count($matches) > 0) {
return $matches[1];
return array('userId' => $matches[1], 'username' => $username);
}
//Get the page ID if we do have a captcha
$regex = '/"pageID":"([0-9]*)"/';
preg_match($regex, $pageContent, $matches);
return $matches[1];
return array('userId' => $matches[1], 'username' => $username);
}
@@ -274,8 +287,4 @@ EOD;
public function getURI(){
return 'http://facebook.com';
}
public function getCacheDuration(){
return 60 * 60 * 3; // 5 minutes
}
}

View File

@@ -19,6 +19,10 @@ class FDroidBridge extends BridgeAbstract {
)
));
public function getIcon() {
return self::URI . 'assets/favicon.ico?v=8j6PKzW9Mk';
}
public function collectData(){
$url = self::URI;
$html = getSimpleHTMLDOM($url)
@@ -45,9 +49,9 @@ class FDroidBridge extends BridgeAbstract {
$item['icon'] = $element->find('img', 0)->src;
$item['summary'] = $element->find('span.package-summary', 0)->plaintext;
$item['content'] = '
<a href="'.$item['uri'].'">
<img alt="" style="max-height:128px" src="'.$item['icon'].'">
</a><br>'.$item['summary'];
<a href="' . $item['uri'] . '">
<img alt="" style="max-height:128px" src="' . $item['icon'] . '">
</a><br>' . $item['summary'];
$this->items[] = $item;
}
}

View File

@@ -2,7 +2,7 @@
class FacebookBridge extends BridgeAbstract {
const MAINTAINER = 'teromene, logmanoriginal';
const NAME = 'Facebook';
const NAME = 'Facebook Bridge';
const URI = 'https://www.facebook.com/';
const CACHE_TIMEOUT = 300; // 5min
const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
@@ -41,23 +41,75 @@ class FacebookBridge extends BridgeAbstract {
'exampleValue' => 'https://www.facebook.com/groups/743149642484225',
'title' => 'Insert group name or facebook group URL'
)
),
'global' => array(
'limit' => array(
'name' => 'Limit',
'type' => 'number',
'required' => false,
'title' => 'Specify the number of items to return (default: -1)',
'defaultValue' => -1
)
)
);
private $authorName = '';
private $groupName = '';
public function getIcon() {
return 'https://static.xx.fbcdn.net/rsrc.php/yo/r/iRmz9lCMBD2.ico';
}
public function getName(){
switch($this->queriedContext) {
case 'User':
if(!empty($this->authorName)) {
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName
. ' - ' . static::NAME;
}
break;
case 'Group':
if(!empty($this->groupName)) {
return $this->groupName . ' - ' . static::NAME;
}
break;
}
return parent::getName();
}
public function getURI() {
$uri = self::URI;
switch($this->queriedContext) {
case 'Group':
// Discover groups via https://www.facebook.com/groups/
// Example group: https://www.facebook.com/groups/sailors.worldwide
$uri .= 'groups/' . $this->sanitizeGroup(filter_var($this->getInput('g'), FILTER_SANITIZE_URL));
break;
case 'User':
// Example user 1: https://www.facebook.com/artetv/
// Example user 2: artetv
$user = $this->sanitizeUser($this->getInput('u'));
if(!strpos($user, '/')) {
$uri .= urlencode($user) . '/posts';
} else {
$uri .= 'pages/' . $user;
}
break;
}
// Request the mobile version to reduce page size (no javascript)
// More information: https://stackoverflow.com/a/11103592
return $uri .= '?_fb_noscript=1';
}
@@ -78,6 +130,12 @@ class FacebookBridge extends BridgeAbstract {
}
$limit = $this->getInput('limit') ?: -1;
if($limit > 0 && count($this->items) > $limit) {
$this->items = array_slice($this->items, 0, $limit);
}
}
#region Group
@@ -121,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);
@@ -249,173 +306,224 @@ class FacebookBridge extends BridgeAbstract {
}
#endregion
#endregion (Group)
private function collectUserData(){
#region User
//Extract a string using start and end delimiters
function extractFromDelimiters($string, $start, $end){
if(strpos($string, $start) !== false) {
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
return $section_retrieved;
/**
* Checks if $user is a valid username or URI and returns the username
*/
private function sanitizeUser($user) {
if (filter_var($user, FILTER_VALIDATE_URL)) {
$urlparts = parse_url($user);
if($urlparts['host'] !== parse_url(self::URI)['host']) {
returnClientError('The host you provided is invalid! Received "'
. $urlparts['host']
. '", expected "'
. parse_url(self::URI)['host']
. '"!');
}
return false;
}
if(!array_key_exists('path', $urlparts)
|| $urlparts['path'] === '/') {
returnClientError('The URL you provided doesn\'t contain the user name!');
}
//Utility function for cleaning a Facebook link
$unescape_fb_link = function($matches){
return explode('/', $urlparts['path'])[1];
} else {
// First character cannot be a forward slash
if(strpos($user, '/') === 0) {
returnClientError('Remove leading slash "/" from the username!');
}
return $user;
}
}
/**
* Bypass external link redirection
*/
private function unescape_fb_link($content){
return preg_replace_callback('/ href=\"([^"]+)\"/i', function($matches){
if(is_array($matches) && count($matches) > 1) {
$link = $matches[1];
if(strpos($link, '/') === 0)
$link = self::URI . $link;
if(strpos($link, 'facebook.com/l.php?u=') !== false)
$link = urldecode(extractFromDelimiters($link, 'facebook.com/l.php?u=', '&'));
return ' href="' . $link . '"';
}
};
}, $content);
}
//Utility function for converting facebook emoticons
$unescape_fb_emote = function($matches){
static $facebook_emoticons = array(
'smile' => ':)',
'frown' => ':(',
'tongue' => ':P',
'grin' => ':D',
'gasp' => ':O',
'wink' => ';)',
'pacman' => ':<',
'grumpy' => '>_<',
'unsure' => ':/',
'cry' => ':\'(',
'kiki' => '^_^',
'glasses' => '8-)',
'sunglasses' => 'B-)',
'heart' => '<3',
'devil' => ']:D',
'angel' => '0:)',
'squint' => '-_-',
'confused' => 'o_O',
'upset' => 'xD',
'colonthree' => ':3',
'like' => '&#x1F44D;');
$len = count($matches);
if ($len > 1)
for ($i = 1; $i < $len; $i++)
foreach ($facebook_emoticons as $name => $emote)
if ($matches[$i] === $name)
return $emote;
return $matches[0];
};
/**
* Remove Facebook's tracking code
*/
private function remove_tracking_codes($content){
return preg_replace_callback('/ href=\"([^"]+)\"/i', function($matches){
if(is_array($matches) && count($matches) > 1) {
$html = null;
$link = $matches[1];
//Handle captcha response sent by the viewer
if(strpos($link, 'facebook.com') !== false) {
if(strpos($link, '?') !== false) {
$link = substr($link, 0, strpos($link, '?'));
}
}
return ' href="' . $link . '"';
}
}, $content);
}
/**
* Convert textual representation of emoticons back to ASCII emoticons.
* i.e. "<i><u>smile emoticon</u></i>" => ":)"
*/
private function unescape_fb_emote($content){
return preg_replace_callback('/<i><u>([^ <>]+) ([^<>]+)<\/u><\/i>/i', function($matches){
static $facebook_emoticons = array(
'smile' => ':)',
'frown' => ':(',
'tongue' => ':P',
'grin' => ':D',
'gasp' => ':O',
'wink' => ';)',
'pacman' => ':<',
'grumpy' => '>_<',
'unsure' => ':/',
'cry' => ':\'(',
'kiki' => '^_^',
'glasses' => '8-)',
'sunglasses' => 'B-)',
'heart' => '<3',
'devil' => ']:D',
'angel' => '0:)',
'squint' => '-_-',
'confused' => 'o_O',
'upset' => 'xD',
'colonthree' => ':3',
'like' => '&#x1F44D;');
$len = count($matches);
if ($len > 1)
for ($i = 1; $i < $len; $i++)
foreach ($facebook_emoticons as $name => $emote)
if ($matches[$i] === $name)
return $emote;
return $matches[0];
}, $content);
}
/**
* Returns the captcha message for the given captcha
*/
private function returnCaptchaMessage($captcha) {
// Save form for submitting after getting captcha response
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
$captcha_fields = array();
foreach ($captcha->find('input, button') as $input) {
$captcha_fields[$input->name] = $input->value;
}
$_SESSION['captcha_fields'] = $captcha_fields;
$_SESSION['captcha_action'] = $captcha->find('form', 0)->action;
// Show captcha filling form to the viewer, proxying the captcha image
$img = base64_encode(getContents($captcha->find('img', 0)->src));
header('Content-Type: text/html', true, 500);
$message = <<<EOD
<form method="post" action="?{$_SERVER['QUERY_STRING']}">
<h2>Facebook captcha challenge</h2>
<p>Unfortunately, rss-bridge cannot fetch the requested page.<br />
Facebook wants rss-bridge to resolve the following captcha:</p>
<p><img src="data:image/png;base64,{$img}" /></p>
<p><b>Response:</b> <input name="captcha_response" placeholder="please fill in" />
<input type="submit" value="Submit!" /></p>
</form>
EOD;
die($message);
}
/**
* Checks if a capture response was received and tries to load the contents
* @return mixed null if no capture response was received, simplhtmldom document otherwise
*/
private function handleCaptchaResponse() {
if (isset($_POST['captcha_response'])) {
if (session_status() == PHP_SESSION_NONE)
session_start();
if (isset($_SESSION['captcha_fields'], $_SESSION['captcha_action'])) {
$captcha_action = $_SESSION['captcha_action'];
$captcha_fields = $_SESSION['captcha_fields'];
$captcha_fields['captcha_response'] = preg_replace('/[^a-zA-Z0-9]+/', '', $_POST['captcha_response']);
$header = array("Content-type:
application/x-www-form-urlencoded\r\nReferer: $captcha_action\r\nCookie: noscript=1\r\n");
$header = array(
'Content-type: application/x-www-form-urlencoded',
'Referer: ' . $captcha_action,
'Cookie: noscript=1'
);
$opts = array(
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => http_build_query($captcha_fields)
);
$html = getContents($captcha_action, $header, $opts);
$html = getSimpleHTMLDOM($captcha_action, $header, $opts)
or returnServerError('Failed to submit captcha response back to Facebook');
if($html === false) {
returnServerError('Failed to submit captcha response back to Facebook');
}
unset($_SESSION['captcha_fields']);
$html = str_get_html($html);
return $html;
}
unset($_SESSION['captcha_fields']);
unset($_SESSION['captcha_action']);
}
//Retrieve page contents
return null;
}
private function collectUserData(){
$html = $this->handleCaptchaResponse();
// Retrieve page contents
if(is_null($html)) {
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n");
// Check if the user provided a fully qualified URL
if (filter_var($this->getInput('u'), FILTER_VALIDATE_URL)) {
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE'));
$urlparts = parse_url($this->getInput('u'));
$html = getSimpleHTMLDOM($this->getURI(), $header)
or returnServerError('No results for this query.');
if($urlparts['host'] !== parse_url(self::URI)['host']) {
returnClientError('The host you provided is invalid! Received "'
. $urlparts['host']
. '", expected "'
. parse_url(self::URI)['host']
. '"!');
}
if(!array_key_exists('path', $urlparts)
|| $urlparts['path'] === '/') {
returnClientError('The URL you provided doesn\'t contain the user name!');
}
$user = explode('/', $urlparts['path'])[1];
$html = getSimpleHTMLDOM(self::URI . urlencode($user) . '?_fb_noscript=1', $header)
or returnServerError('No results for this query.');
} else {
// First character cannot be a forward slash
if(strpos($this->getInput('u'), '/') === 0) {
returnClientError('Remove leading slash "/" from the username!');
}
if(!strpos($this->getInput('u'), '/')) {
$html = getSimpleHTMLDOM(self::URI . urlencode($this->getInput('u')) . '?_fb_noscript=1', $header)
or returnServerError('No results for this query.');
} else {
$html = getSimpleHTMLDOM(self::URI . 'pages/' . $this->getInput('u') . '?_fb_noscript=1', $header)
or returnServerError('No results for this query.');
}
}
}
//Handle captcha form?
// Handle captcha form?
$captcha = $html->find('div.captcha_interstitial', 0);
if (!is_null($captcha)) {
//Save form for submitting after getting captcha response
if (session_status() == PHP_SESSION_NONE)
session_start();
$captcha_fields = array();
foreach ($captcha->find('input, button') as $input)
$captcha_fields[$input->name] = $input->value;
$_SESSION['captcha_fields'] = $captcha_fields;
$_SESSION['captcha_action'] = $captcha->find('form', 0)->action;
//Show captcha filling form to the viewer, proxying the captcha image
$img = base64_encode(getContents($captcha->find('img', 0)->src));
http_response_code(500);
header('Content-Type: text/html');
$message = <<<EOD
<form method="post" action="?{$_SERVER['QUERY_STRING']}">
<h2>Facebook captcha challenge</h2>
<p>Unfortunately, rss-bridge cannot fetch the requested page.<br />
Facebook wants rss-bridge to resolve the following captcha:</p>
<p><img src="data:image/png;base64,{$img}" /></p>
<p><b>Response:</b> <input name="captcha_response" placeholder="please fill in" />
<input type="submit" value="Submit!" /></p>
</form>
EOD;
die($message);
if (!is_null($captcha)) {
$this->returnCaptchaMessage($captcha);
}
//No captcha? We can carry on retrieving page contents :)
//First, we check wether the page is public or not
// No captcha? We can carry on retrieving page contents :)
// First, we check wether the page is public or not
$loginForm = $html->find('._585r', 0);
if($loginForm != null) {
returnServerError('You must be logged in to view this page. This is not supported by RSS-Bridge.');
}
@@ -424,16 +532,14 @@ EOD;
->find('#pagelet_timeline_main_column')[0]
->children(0)
->children(0)
->children(0)
->next_sibling()
->children(0);
if(isset($element)) {
$author = str_replace(' | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
$profilePic = 'https://graph.facebook.com/'
. $this->getInput('u')
. '/picture?width=200&amp;height=200';
$author = str_replace(' - Posts | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
$profilePic = $html->find('meta[property="og:image"]', 0)->content;
$this->authorName = $author;
@@ -468,16 +574,30 @@ EOD;
if(count($post->find('abbr')) > 0) {
//Retrieve post contents
$content = preg_replace(
'/(?i)><div class=\"clearfix([^>]+)>(.+?)div\ class=\"userContent\"/i',
'',
$post);
$content = $post->find('.userContentWrapper', 0);
$content = preg_replace(
'/(?i)><div class=\"_59tj([^>]+)>(.+?)<\/div><\/div><a/i',
'',
$content);
// This array specifies filters applied to all posts in order of appearance
$content_filters = array(
'._5mly', // Remove embedded videos (the preview image remains)
'._2ezg', // Remove "Views ..."
'.hidden_elem', // Remove hidden elements (they are hidden anyway)
);
foreach($content_filters as $filter) {
foreach($content->find($filter) as $subject) {
$subject->outertext = '';
}
}
// Change origin tag for embedded media from div to paragraph
foreach($content->find('._59tj') as $subject) {
$subject->outertext = '<p>' . $subject->innertext . '</p>';
}
// Change title tag for embedded media from anchor to paragraph
foreach($content->find('._3n1k a') as $anchor) {
$anchor->outertext = '<p>' . $anchor->innertext . '</p>';
}
$content = preg_replace(
'/(?i)><div class=\"_3dp([^>]+)>(.+?)div\ class=\"[^u]+userContent\"/i',
@@ -489,13 +609,18 @@ EOD;
'',
$content);
//Remove html nodes, keep only img, links, basic formatting
// Remove "SpSonsSoriSsés"
$content = preg_replace(
'/(?iU)<a [^>]+ href="#" role="link" [^>}]+>.+<\/a>/iU',
'',
$content);
// Remove html nodes, keep only img, links, basic formatting
$content = strip_tags($content, '<a><img><i><u><br><p>');
//Adapt link hrefs: convert relative links into absolute links and bypass external link redirection
$content = preg_replace_callback('/ href=\"([^"]+)\"/i', $unescape_fb_link, $content);
$content = $this->unescape_fb_link($content);
//Clean useless html tag properties and fix link closing tags
// Clean useless html tag properties and fix link closing tags
foreach (array(
'onmouseover',
'onclick',
@@ -508,68 +633,59 @@ EOD;
'aria-[^=]*',
'role',
'rel',
'id') as $property_name)
$content = preg_replace('/ ' . $property_name . '=\"[^"]*\"/i', '', $content);
'id') as $property_name) {
$content = preg_replace('/ ' . $property_name . '=\"[^"]*\"/i', '', $content);
}
$content = preg_replace('/<\/a [^>]+>/i', '</a>', $content);
//Convert textual representation of emoticons eg
//"<i><u>smile emoticon</u></i>" back to ASCII emoticons eg ":)"
$content = preg_replace_callback(
'/<i><u>([^ <>]+) ([^<>]+)<\/u><\/i>/i',
$unescape_fb_emote,
$content
);
$this->unescape_fb_emote($content);
//Retrieve date of the post
// Restore links in the post before further parsing
$post = defaultLinkTo($post, self::URI);
// Restore links in the content before adding to the item
$content = defaultLinkTo($content, self::URI);
$content = $this->remove_tracking_codes($content);
// Retrieve date of the post
$date = $post->find('abbr')[0];
if(isset($date) && $date->hasAttribute('data-utime')) {
$date = $date->getAttribute('data-utime');
} else {
$date = 0;
}
//Build title from username and content
$title = $author;
if(strlen($title) > 24)
$title = substr($title, 0, strpos(wordwrap($title, 24), "\n")) . '...';
$title = $title . ' | ' . strip_tags($content);
// Build title from content
$title = strip_tags($post->find('.userContent', 0)->innertext);
if(strlen($title) > 64)
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
$uri = self::URI . $post->find('abbr')[0]->parent()->getAttribute('href');
$uri = $post->find('abbr')[0]->parent()->getAttribute('href');
if (false !== strpos($uri, '?')) {
$uri = substr($uri, 0, strpos($uri, '?'));
}
//Build and add final item
$item['uri'] = htmlspecialchars_decode($uri);
$item['content'] = htmlspecialchars_decode($content);
$item['title'] = $title;
$item['author'] = $author;
$item['uri'] = htmlspecialchars_decode($uri, ENT_QUOTES);
$item['content'] = htmlspecialchars_decode($content, ENT_QUOTES);
$item['title'] = htmlspecialchars_decode($title, ENT_QUOTES);
$item['author'] = htmlspecialchars_decode($author, ENT_QUOTES);
$item['timestamp'] = $date;
if(strpos($item['content'], '<img') === false) {
$item['enclosures'] = array($profilePic);
}
$this->items[] = $item;
}
}
}
}
}
#endregion (User)
public function getName(){
switch($this->queriedContext) {
case 'User':
if(!empty($this->authorName)) {
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName
. ' - Facebook Bridge';
}
break;
case 'Group':
if(!empty($this->groupName)) {
return $this->groupName . ' - Facebook Bridge';
}
break;
}
return parent::getName();
}
}

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(

View File

@@ -7,6 +7,10 @@ class FierPandaBridge extends BridgeAbstract {
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'Returns latest articles from Fier Panda.';
public function getIcon() {
return self::URI . 'wp-content/themes/fier-panda/img/favicon.png';
}
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)

View File

@@ -6,6 +6,7 @@ class FilterBridge extends FeedExpander {
const NAME = 'Filter';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Filters a feed of your choice';
const URI = 'https://github.com/rss-bridge/rss-bridge';
const PARAMETERS = array(array(
'url' => array(
@@ -93,7 +94,7 @@ class FilterBridge extends FeedExpander {
}
try{
$this->collectExpandableDatas($this->getURI());
} catch (HttpException $e) {
} catch (Exception $e) {
$this->collectExpandableDatas($this->getURI());
}
}

View File

@@ -0,0 +1,82 @@
<?php
class FindACrewBridge extends BridgeAbstract {
const MAINTAINER = 'couraudt';
const NAME = 'Find A Crew Bridge';
const URI = 'https://www.findacrew.net';
const DESCRIPTION = 'Returns the newest sailing offers.';
const PARAMETERS = array(
array(
'type' => array(
'name' => 'Type of search',
'title' => 'Choose between finding a boat or a crew',
'type' => 'list',
'values' => array(
'Find a boat' => 'boat',
'Find a crew' => 'crew'
)
),
'long' => array(
'name' => 'Longitude of the searched location',
'title' => 'Center the search at that longitude (e.g: -42.02)'
),
'lat' => array(
'name' => 'Latitude of the searched location',
'title' => 'Center the search at that latitude (e.g: 12.42)'
),
'distance' => array(
'name' => 'Limit boundary of search in KM',
'title' => 'Boundary of the search in kilometers when using longitude and latitude'
)
)
);
public function collectData() {
$url = $this->getURI();
if ($this->getInput('type') == 'boat') {
$data = array('SrhLstBtAction' => 'Create');
} else {
$data = array('SrhLstCwAction' => 'Create');
}
if ($this->getInput('long') && $this->getInput('lat')) {
$data['real_LocSrh_Lng'] = $this->getInput('long');
$data['real_LocSrh_Lat'] = $this->getInput('lat');
if ($this->getInput('distance')) {
$data['LocDis'] = (int)$this->getInput('distance') * 1000;
}
}
$header = array(
'Content-Type: application/x-www-form-urlencoded'
);
$opts = array(
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query($data) . "\n"
);
$html = getSimpleHTMLDOM($url, $header, $opts) or returnClientError('No results for this query.');
$annonces = $html->find('.css_SrhRst');
foreach ($annonces as $annonce) {
$item = array();
$img = parent::getURI() . $annonce->find('.css_LstPic img', 0)->getAttribute('src');
$item['title'] = $annonce->find('.css_LstCtrls span', 0)->plaintext;
$item['uri'] = parent::getURI() . $annonce->find('.css_PnlCtrls a', 0)->href;
$content = $annonce->find('.css_LstDtl div', 2)->innertext;
$item['content'] = "<img src='$img' /><br>$content";
$item['enclosures'] = array($img);
$item['categories'] = array($annonce->find('.css_AccLocCur', 0)->plaintext);
$this->items[] = $item;
}
}
public function getURI() {
$uri = parent::getURI();
// Those params must be in the URL
$uri .= '/en/' . $this->getInput('type') . '/search?srhtyp=srhrst&mdl=2';
return $uri;
}
}

View File

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

1
bridges/ForGifsBridge.php Executable file → Normal file
View File

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

View File

@@ -69,7 +69,7 @@ class FourchanBridge extends BridgeAbstract {
. '" src="'
. $item['imageThumb']
. '" /></a><br>'
.$item['content'];
. $item['content'];
}
$this->items[] = $item;
}

View File

@@ -3,7 +3,7 @@ class FuturaSciencesBridge extends FeedExpander {
const MAINTAINER = 'ORelio';
const NAME = 'Futura-Sciences Bridge';
const URI = 'http://www.futura-sciences.com/';
const URI = 'https://www.futura-sciences.com/';
const DESCRIPTION = 'Returns the newest articles.';
const PARAMETERS = array( array(
@@ -90,42 +90,11 @@ class FuturaSciencesBridge extends FeedExpander {
or returnServerError('Could not request Futura-Sciences: ' . $item['uri']);
$item['content'] = $this->extractArticleContent($article);
$author = $this->extractAuthor($article);
$item['author'] = empty($author) ? $item['author'] : $author;
if (!empty($author))
$item['author'] = $author;
return $item;
}
private function stripWithDelimiters($string, $start, $end){
while(strpos($string, $start) !== false) {
$section_to_remove = substr($string, strpos($string, $start));
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
$string = str_replace($section_to_remove, '', $string);
} return $string;
}
private function stripRecursiveHTMLSection($string, $tag_name, $tag_start){
$open_tag = '<' . $tag_name;
$close_tag = '</' . $tag_name . '>';
$close_tag_length = strlen($close_tag);
if(strpos($tag_start, $open_tag) === 0) {
while(strpos($string, $tag_start) !== false) {
$max_recursion = 100;
$section_to_remove = null;
$section_start = strpos($string, $tag_start);
$search_offset = $section_start;
do {
$max_recursion--;
$section_end = strpos($string, $close_tag, $search_offset);
$search_offset = $section_end + $close_tag_length;
$section_to_remove = substr($string, $section_start, $section_end - $section_start + $close_tag_length);
$open_tag_count = substr_count($section_to_remove, $open_tag);
$close_tag_count = substr_count($section_to_remove, $close_tag);
} while ($open_tag_count > $close_tag_count && $max_recursion > 0);
$string = str_replace($section_to_remove, '', $string);
}
}
return $string;
}
private function extractArticleContent($article){
$contents = $article->find('section.article-text-classic', 0)->innertext;
$headline = trim($article->find('p.description', 0)->plaintext);
@@ -137,6 +106,7 @@ class FuturaSciencesBridge extends FeedExpander {
'<div class="sharebar2',
'<div class="diaporamafullscreen"',
'<div class="module social-button',
'<div class="module social-share',
'<div style="margin-bottom:10px;" class="noprint"',
'<div class="ficheprevnext',
'<div class="bar noprint',
@@ -148,16 +118,17 @@ class FuturaSciencesBridge extends FeedExpander {
'<div id="forumcomments',
'<div ng-if="active"'
) as $div_start) {
$contents = $this->stripRecursiveHTMLSection($contents, 'div', $div_start);
$contents = stripRecursiveHTMLSection($contents, 'div', $div_start);
}
$contents = $this->stripWithDelimiters($contents, '<hr ', '/>');
$contents = $this->stripWithDelimiters($contents, '<p class="content-date', '</p>');
$contents = $this->stripWithDelimiters($contents, '<h1 class="content-title', '</h1>');
$contents = $this->stripWithDelimiters($contents, 'fs:definition="', '"');
$contents = $this->stripWithDelimiters($contents, 'fs:xt:clicktype="', '"');
$contents = $this->stripWithDelimiters($contents, 'fs:xt:clickname="', '"');
$contents = $this->stripWithDelimiters($contents, '<script ', '</script>');
$contents = stripWithDelimiters($contents, '<hr ', '/>');
$contents = stripWithDelimiters($contents, '<p class="content-date', '</p>');
$contents = stripWithDelimiters($contents, '<h1 class="content-title', '</h1>');
$contents = stripWithDelimiters($contents, 'fs:definition="', '"');
$contents = stripWithDelimiters($contents, 'fs:xt:clicktype="', '"');
$contents = stripWithDelimiters($contents, 'fs:xt:clickname="', '"');
$contents = StripWithDelimiters($contents, '<section class="module-toretain module-propal-nl', '</section>');
$contents = stripWithDelimiters($contents, '<script ', '</script>');
return $headline . trim($contents);
}

View File

@@ -20,50 +20,58 @@ class GBAtempBridge extends BridgeAbstract {
)
));
private function extractFromDelimiters($string, $start, $end){
if(strpos($string, $start) !== false) {
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
return $section_retrieved;
}
return false;
}
private function stripWithDelimiters($string, $start, $end){
while(strpos($string, $start) !== false) {
$section_to_remove = substr($string, strpos($string, $start));
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
$string = str_replace($section_to_remove, '', $string);
}
return $string;
}
private function buildItem($uri, $title, $author, $timestamp, $content){
private function buildItem($uri, $title, $author, $timestamp, $thumbnail, $content){
$item = array();
$item['uri'] = $uri;
$item['title'] = $title;
$item['author'] = $author;
$item['timestamp'] = $timestamp;
$item['content'] = $content;
if (!empty($thumbnail)) {
$item['enclosures'] = array($thumbnail);
}
return $item;
}
private function cleanupPostContent($content, $site_url){
$content = str_replace(':arrow:', '&#x27a4;', $content);
$content = str_replace('href="attachments/', 'href="'.$site_url.'attachments/', $content);
$content = $this->stripWithDelimiters($content, '<script', '</script>');
$content = str_replace('href="attachments/', 'href="' . $site_url . 'attachments/', $content);
$content = stripWithDelimiters($content, '<script', '</script>');
return $content;
}
private function findItemDate($item){
$time = 0;
$dateField = $item->find('abbr.DateTime', 0);
if (is_object($dateField)) {
$time = intval(
extractFromDelimiters(
$dateField->outertext,
'data-time="',
'"'
)
);
} else {
$dateField = $item->find('span.DateTime', 0);
$time = DateTime::createFromFormat(
'M j, Y \a\t g:i A',
extractFromDelimiters(
$dateField->outertext,
'title="',
'"'
)
)->getTimestamp();
}
return $time;
}
private function fetchPostContent($uri, $site_url){
$html = getSimpleHTMLDOM($uri);
$html = getSimpleHTMLDOMCached($uri);
if(!$html) {
return 'Could not request GBAtemp ' . $uri;
return 'Could not request GBAtemp: ' . $uri;
}
$content = $html->find('div.messageContent', 0)->innertext;
$content = $html->find('div.messageContent, blockquote.baseHtml', 0)->innertext;
return $this->cleanupPostContent($content, $site_url);
}
@@ -76,70 +84,56 @@ class GBAtempBridge extends BridgeAbstract {
case 'N':
foreach($html->find('li[class=news_item full]') as $newsItem) {
$url = self::URI . $newsItem->find('a', 0)->href;
$time = intval(
$this->extractFromDelimiters(
$newsItem->find('abbr.DateTime', 0)->outertext,
'data-time="',
'"'
)
);
$img = $this->getURI() . $newsItem->find('img', 0)->src . '#.image';
$time = $this->findItemDate($newsItem);
$author = $newsItem->find('a.username', 0)->plaintext;
$title = $newsItem->find('a', 1)->plaintext;
$content = $this->fetchPostContent($url, self::URI);
$this->items[] = $this->buildItem($url, $title, $author, $time, $content);
$this->items[] = $this->buildItem($url, $title, $author, $time, $img, $content);
unset($newsItem); // Some items are heavy, freeing the item proactively helps saving memory
}
break;
case 'R':
foreach($html->find('li.portal_review') as $reviewItem) {
$url = self::URI . $reviewItem->find('a', 0)->href;
$img = $this->getURI() . extractFromDelimiters($reviewItem->find('a', 0)->style, 'image:url(', ')');
$title = $reviewItem->find('span.review_title', 0)->plaintext;
$content = getSimpleHTMLDOM($url)
or returnServerError('Could not request GBAtemp: ' . $uri);
$author = $content->find('a.username', 0)->plaintext;
$time = intval(
$this->extractFromDelimiters(
$content->find('abbr.DateTime', 0)->outertext,
'data-time="',
'"'
)
);
$time = $this->findItemDate($content);
$intro = '<p><b>' . ($content->find('div#review_intro', 0)->plaintext) . '</b></p>';
$review = $content->find('div#review_main', 0)->innertext;
$subheader = '<p><b>' . $content->find('div.review_subheader', 0)->plaintext . '</b></p>';
$procons = $content->find('table.review_procons', 0)->outertext;
$scores = $content->find('table.reviewscores', 0)->outertext;
$content = $this->cleanupPostContent($intro . $review . $subheader . $procons . $scores, self::URI);
$this->items[] = $this->buildItem($url, $title, $author, $time, $content);
$this->items[] = $this->buildItem($url, $title, $author, $time, $img, $content);
unset($reviewItem); // Free up memory
}
break;
case 'T':
foreach($html->find('li.portal-tutorial') as $tutorialItem) {
$url = self::URI . $tutorialItem->find('a', 0)->href;
$title = $tutorialItem->find('a', 0)->plaintext;
$time = intval(
$this->extractFromDelimiters(
$tutorialItem->find('abbr.DateTime', 0)->outertext,
'data-time="',
'"'
)
);
$time = $this->findItemDate($tutorialItem);
$author = $tutorialItem->find('a.username', 0)->plaintext;
$content = $this->fetchPostContent($url, self::URI);
$this->items[] = $this->buildItem($url, $title, $author, $time, $content);
$this->items[] = $this->buildItem($url, $title, $author, $time, null, $content);
unset($tutorialItem); // Free up memory
}
break;
case 'F':
foreach($html->find('li.rc_item') as $postItem) {
$url = self::URI . $postItem->find('a', 1)->href;
$title = $postItem->find('a', 1)->plaintext;
$time = intval(
$this->extractFromDelimiters(
$postItem->find('abbr.DateTime', 0)->outertext,
'data-time="',
'"'
)
);
$time = $this->findItemDate($postItem);
$author = $postItem->find('a.username', 0)->plaintext;
$content = $this->fetchPostContent($url, self::URI);
$this->items[] = $this->buildItem($url, $title, $author, $time, $content);
$this->items[] = $this->buildItem($url, $title, $author, $time, null, $content);
unset($postItem); // Free up memory
}
break;
}
}

65
bridges/GOGBridge.php Normal file
View File

@@ -0,0 +1,65 @@
<?php
class GOGBridge extends BridgeAbstract {
const NAME = 'GOGBridge';
const MAINTAINER = 'teromene';
const URI = 'https://gog.com';
const DESCRIPTION = 'Returns the latest releases from GOG.com';
public function collectData() {
$values = getContents('https://www.gog.com/games/ajax/filtered?limit=25&sort=new') or
die('Unable to get the news pages from GOG !');
$decodedValues = json_decode($values);
$limit = 0;
foreach($decodedValues->products as $game) {
$item = array();
$item['author'] = $game->developer . ' / ' . $game->publisher;
$item['title'] = $game->title;
$item['id'] = $game->id;
$item['uri'] = self::URI . $game->url;
$item['content'] = $this->buildGameContentPage($game);
$item['timestamp'] = $game->globalReleaseDate;
foreach($game->gallery as $image) {
$item['enclosures'][] = $image . '.jpg';
}
$this->items[] = $item;
$limit += 1;
if($limit == 10) break;
}
}
private function buildGameContentPage($game) {
$gameDescriptionText = getContents('https://api.gog.com/products/' . $game->id . '?expand=description') or
die('Unable to get game description from GOG !');
$gameDescriptionValue = json_decode($gameDescriptionText);
$content = 'Genres: ';
$content .= implode(', ', $game->genres);
$content .= '<br />Supported Platforms: ';
if($game->worksOn->Windows) {
$content .= 'Windows ';
}
if($game->worksOn->Mac) {
$content .= 'Mac ';
}
if($game->worksOn->Linux) {
$content .= 'Linux ';
}
$content .= '<br />' . $gameDescriptionValue->description->full;
return $content;
}
}

View File

@@ -0,0 +1,123 @@
<?php
/**
* An extension of the previous SexactuBridge to cover the whole GQMagazine.
* This one taks a page (as an example sexe/news or journaliste/maia-mazaurette) which is to be configured,
* reads all the articles visible on that page, and make a stream out of it.
* @author nicolas-delsaux
*
*/
class GQMagazineBridge extends BridgeAbstract
{
const MAINTAINER = 'Riduidel';
const NAME = 'GQMagazine';
// URI is no more valid, since we can address the whole gq galaxy
const URI = 'https://www.gqmagazine.fr';
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,
'defaultValue' => self::DEFAULT_DOMAIN
),
'page' => array(
'name' => 'Initial page to load',
'required' => true,
'exampleValue' => 'sexe/news'
),
));
const REPLACED_ATTRIBUTES = array(
'href' => 'href',
'src' => 'src',
'data-original' => 'src'
);
private function getDomain() {
$domain = $this->getInput('domain');
if (empty($domain))
$domain = self::DEFAULT_DOMAIN;
if (strpos($domain, '://') === false)
$domain = 'https://' . $domain;
return $domain;
}
public function getURI()
{
return $this->getDomain() . '/' . $this->getInput('page');
}
public function collectData()
{
$html = getSimpleHTMLDOM($this->getURI()) or returnServerError('Could not request ' . $this->getURI());
// Since GQ don't want simple class scrapping, let's do it the hard way and ... discover content !
$main = $html->find('main', 0);
foreach ($main->find('a') as $link) {
$uri = $link->href;
$title = $link->find('h2', 0);
$date = $link->find('time', 0);
$item = array();
$author = $link->find('span[itemprop=name]', 0);
$item['author'] = $author->plaintext;
$item['title'] = $title->plaintext;
if(substr($uri, 0, 1) === 'h') { // absolute uri
$item['uri'] = $uri;
} else if(substr($uri, 0, 1) === '/') { // domain relative url
$item['uri'] = $this->getDomain() . $uri;
} else {
$item['uri'] = $this->getDomain() . '/' . $uri;
}
$article = $this->loadFullArticle($item['uri']);
if($article) {
$item['content'] = $this->replaceUriInHtmlElement($article);
} else {
$item['content'] = "<strong>Article body couldn't be loaded</strong>. It must be a bug!";
}
$short_date = $date->datetime;
$item['timestamp'] = strtotime($short_date);
$this->items[] = $item;
}
}
/**
* Loads the full article and returns the contents
* @param $uri The article URI
* @return The article content
*/
private function loadFullArticle($uri){
$html = getSimpleHTMLDOMCached($uri);
// Once again, that generated css classes madness is an obstacle ... which i can go over easily
foreach($html->find('div') as $div) {
// List the CSS classes of that div
$classes = $div->class;
// I can't directly lookup that class since GQ since to generate random names like "ArticleBodySection-fkggUW"
if(strpos($classes, 'ArticleBodySection') !== false) {
return $div;
}
}
return null;
}
/**
* Replaces all relative URIs with absolute ones
* @param $element A simplehtmldom element
* @return The $element->innertext with all URIs replaced
*/
private function replaceUriInHtmlElement($element){
$returned = $element->innertext;
foreach (self::REPLACED_ATTRIBUTES as $initial => $final) {
$returned = str_replace($initial . '="/', $final . '="' . self::URI . '/', $returned);
}
return $returned;
}
}

View File

@@ -54,7 +54,7 @@ class GitHubGistBridge extends BridgeAbstract {
DEFAULT_SPAN_TEXT)
or returnServerError('Could not request ' . $this->getURI());
$html = defaultLinkTo($html, static::URI);
$html = defaultLinkTo($html, $this->getURI());
$fileinfo = $html->find('[class="file-info"]', 0)
or returnServerError('Could not find file info!');
@@ -69,7 +69,7 @@ class GitHubGistBridge extends BridgeAbstract {
foreach($comments as $comment) {
$uri = $comment->find('a[href^=#gistcomment]', 0)
$uri = $comment->find('a[href*=#gistcomment]', 0)
or returnServerError('Could not find comment anchor!');
$title = $comment->find('div[class="unminimized-comment"] h3[class="timeline-comment-header-text"]', 0)
@@ -86,7 +86,7 @@ class GitHubGistBridge extends BridgeAbstract {
$item = array();
$item['uri'] = $this->getURI() . $uri->href;
$item['uri'] = $uri->href;
$item['title'] = str_replace('commented', 'commented on', $title->plaintext);
$item['timestamp'] = strtotime($datetime->datetime);
$item['author'] = '<a href="' . $author->href . '">' . $author->plaintext . '</a>';
@@ -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
)
)
);
@@ -37,10 +37,9 @@ class GithubIssueBridge extends BridgeAbstract {
$name = $this->getInput('u') . '/' . $this->getInput('p');
switch($this->queriedContext) {
case 'Project Issues':
$prefix = static::NAME . 's for ';
if($this->getInput('c')) {
$prefix = static::NAME . 's comments for ';
} else {
$prefix = static::NAME . 's for ';
}
$name = $prefix . $name;
break;
@@ -53,8 +52,9 @@ class GithubIssueBridge extends BridgeAbstract {
}
public function getURI(){
if(!is_null($this->getInput('u')) && !is_null($this->getInput('p'))) {
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues';
if(null !== $this->getInput('u') && null !== $this->getInput('p')) {
$uri = static::URI . $this->getInput('u') . '/'
. $this->getInput('p') . '/issues';
if($this->queriedContext === 'Issue comments') {
$uri .= '/' . $this->getInput('i');
} elseif($this->getInput('c')) {
@@ -66,54 +66,54 @@ class GithubIssueBridge extends BridgeAbstract {
return parent::getURI();
}
protected function extractIssueComment($issueNbr, $title, $comment){
$class = $comment->getAttribute('class');
$classes = explode(' ', $class);
$event = false;
if(in_array('discussion-item', $classes)) {
$event = true;
}
$author = 'unknown';
if($comment->find('.author', 0)) {
$author = $comment->find('.author', 0)->plaintext;
}
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues/' . $issueNbr;
protected function extractIssueEvent($issueNbr, $title, $comment){
$comment = $comment->firstChild();
if(!$event) {
$comment = $comment->nextSibling();
}
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p')
. '/issues/' . $issueNbr . '#' . $comment->getAttribute('id');
if($event) {
$title .= ' / ' . substr($class, strpos($class, 'discussion-item-') + strlen('discussion-item-'));
if(!$comment->hasAttribute('id')) {
$items = array();
$timestamp = strtotime($comment->find('relative-time', 0)->getAttribute('datetime'));
$content = $comment->innertext;
while($comment = $comment->nextSibling()) {
$item = array();
$item['author'] = $author;
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = $timestamp;
$item['content'] = $content . '<p>' . $comment->children(1)->innertext . '</p>';
$item['uri'] = $uri . '#' . $comment->children(1)->getAttribute('id');
$items[] = $item;
}
return $items;
$author = $comment->find('.author', 0)->plaintext;
$title .= ' / ' . trim($comment->plaintext);
$content = $title;
if (null !== $comment->nextSibling()) {
$content = $comment->nextSibling()->innertext;
if ($comment->nextSibling()->nodeName() === 'span') {
$content = $comment->nextSibling()->nextSibling()->innertext;
}
$content = $comment->parent()->innertext;
} else {
$title .= ' / ' . trim($comment->firstChild()->plaintext);
$content = '<pre>' . $comment->find('.comment-body', 0)->innertext . '</pre>';
}
$item = array();
$item['author'] = $author;
$item['uri'] = $uri . '#' . $comment->getAttribute('id');
$item['uri'] = $uri;
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = strtotime($comment->find('relative-time', 0)->getAttribute('datetime'));
$item['timestamp'] = strtotime(
$comment->find('relative-time', 0)->getAttribute('datetime')
);
$item['content'] = $content;
return $item;
}
protected function extractIssueComment($issueNbr, $title, $comment){
$uri = static::URI . $this->getInput('u') . '/'
. $this->getInput('p') . '/issues/' . $issueNbr;
$author = $comment->find('.author', 0)->plaintext;
$title .= ' / ' . trim(
$comment->find('.comment .timeline-comment-header-text', 0)->plaintext
);
$content = $comment->find('.comment-body', 0)->innertext;
$item = array();
$item['author'] = $author;
$item['uri'] = $uri
. '#' . $comment->firstChild()->nextSibling()->getAttribute('id');
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = strtotime(
$comment->find('relative-time', 0)->getAttribute('datetime')
);
$item['content'] = $content;
return $item;
}
@@ -121,17 +121,29 @@ class GithubIssueBridge extends BridgeAbstract {
protected function extractIssueComments($issue){
$items = array();
$title = $issue->find('.gh-header-title', 0)->plaintext;
$issueNbr = trim(substr($issue->find('.gh-header-number', 0)->plaintext, 1));
$issueNbr = trim(
substr($issue->find('.gh-header-number', 0)->plaintext, 1)
);
$comments = $issue->find('.js-discussion', 0);
foreach($comments->children() as $comment) {
if (!$comment->hasChildNodes()) {
continue;
}
$comment = $comment->firstChild();
$classes = explode(' ', $comment->getAttribute('class'));
if(in_array('discussion-item', $classes)
|| in_array('timeline-comment-wrapper', $classes)) {
if (in_array('timeline-comment-wrapper', $classes)) {
$item = $this->extractIssueComment($issueNbr, $title, $comment);
if(array_keys($item) !== range(0, count($item) - 1)) {
$item = array($item);
$items[] = $item;
continue;
}
while (in_array('discussion-item', $classes)) {
$item = $this->extractIssueEvent($issueNbr, $title, $comment);
$items[] = $item;
$comment = $comment->nextSibling();
if (null == $comment) {
break;
}
$items = array_merge($items, $item);
$classes = explode(' ', $comment->getAttribute('class'));
}
}
return $items;
@@ -139,7 +151,9 @@ class GithubIssueBridge extends BridgeAbstract {
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('No results for Github Issue ' . $this->getURI());
or returnServerError(
'No results for Github Issue ' . $this->getURI()
);
switch($this->queriedContext) {
case 'Issue comments':
@@ -148,31 +162,40 @@ class GithubIssueBridge extends BridgeAbstract {
case 'Project Issues':
foreach($html->find('.js-active-navigation-container .js-navigation-item') as $issue) {
$info = $issue->find('.opened-by', 0);
$issueNbr = substr(trim($info->plaintext), 1, strpos(trim($info->plaintext), ' '));
$issueNbr = substr(
trim($info->plaintext), 1, strpos(trim($info->plaintext), ' ')
);
$item = array();
$item['content'] = '';
if($this->getInput('c')) {
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues/' . $issueNbr;
$uri = static::URI . $this->getInput('u')
. '/' . $this->getInput('p') . '/issues/' . $issueNbr;
$issue = getSimpleHTMLDOMCached($uri, static::CACHE_TIMEOUT);
if($issue) {
$this->items = array_merge($this->items, $this->extractIssueComments($issue));
$this->items = array_merge(
$this->items,
$this->extractIssueComments($issue)
);
continue;
}
$item['content'] = 'Can not extract comments from ' . $uri;
}
$item['author'] = $info->find('a', 0)->plaintext;
$item['timestamp'] = strtotime($info->find('relative-time', 0)->getAttribute('datetime'));
$item['timestamp'] = strtotime(
$info->find('relative-time', 0)->getAttribute('datetime')
);
$item['title'] = html_entity_decode(
$issue->find('.js-navigation-open', 0)->plaintext,
ENT_QUOTES,
'UTF-8'
);
$comments = $issue->find('.col-5', 0)->plaintext;
$comments = trim($issue->find('.col-5', 0)->plaintext);
$item['content'] .= "\n" . 'Comments: ' . ($comments ? $comments : '0');
$item['uri'] = self::URI . $issue->find('.js-navigation-open', 0)->getAttribute('href');
$item['uri'] = self::URI
. $issue->find('.js-navigation-open', 0)->getAttribute('href');
$this->items[] = $item;
}
break;
@@ -180,7 +203,11 @@ class GithubIssueBridge extends BridgeAbstract {
array_walk($this->items, function(&$item){
$item['content'] = preg_replace('/\s+/', ' ', $item['content']);
$item['content'] = str_replace('href="/', 'href="' . static::URI, $item['content']);
$item['content'] = str_replace(
'href="/',
'href="' . static::URI,
$item['content']
);
$item['content'] = str_replace(
'href="#',
'href="' . substr($item['uri'], 0, strpos($item['uri'], '#') + 1),

View File

@@ -34,13 +34,29 @@ class GithubSearchBridge extends BridgeAbstract {
$title = $element->find('h3', 0)->plaintext;
$item['title'] = $title;
if (count($element->find('p')) == 2) {
$content = $element->find('p', 0)->innertext;
// Description
if (count($element->find('p.d-inline-block')) != 0) {
$content = $element->find('p.d-inline-block', 0)->innertext;
} else{
$content = '';
$content = 'No description';
}
$item['content'] = $content;
// Tags
$content = $content . '<br />';
$tags = $element->find('a.topic-tag');
$tags_array = array();
if (count($tags) != 0) {
$content = $content . 'Tags : ';
foreach($tags as $tag_element) {
$tag_link = 'https://github.com' . $tag_element->href;
$tag_name = trim($tag_element->innertext);
$content = $content . '<a href="' . $tag_link . '">' . $tag_name . '</a> ';
array_push($tags_array, $tag_element->innertext);
}
}
$item['categories'] = $tags_array;
$item['content'] = $content;
$date = $element->find('relative-time', 0)->datetime;
$item['timestamp'] = strtotime($date);

221
bridges/GlassdoorBridge.php Normal file
View File

@@ -0,0 +1,221 @@
<?php
class GlassdoorBridge extends BridgeAbstract {
// Contexts
const CONTEXT_BLOG = 'Blogs';
const CONTEXT_REVIEW = 'Company Reviews';
const CONTEXT_GLOBAL = 'global';
// Global context parameters
const PARAM_LIMIT = 'limit';
// Blog context parameters
const PARAM_BLOG_TYPE = 'blog_type';
const PARAM_BLOG_FULL = 'full_article';
const BLOG_TYPE_HOME = 'Home';
const BLOG_TYPE_COMPANIES_HIRING = 'Companies Hiring';
const BLOG_TYPE_CAREER_ADVICE = 'Career Advice';
const BLOG_TYPE_INTERVIEWS = 'Interviews';
const BLOG_TYPE_GUIDE = 'Guides';
// Review context parameters
const PARAM_REVIEW_COMPANY = 'company';
const MAINTAINER = 'logmanoriginal';
const NAME = 'Glassdoor Bridge';
const URI = 'https://www.glassdoor.com/';
const DESCRIPTION = 'Returns feeds for blog posts and company reviews';
const CACHE_TIMEOUT = 86400; // 24 hours
const PARAMETERS = array(
self::CONTEXT_BLOG => array(
self::PARAM_BLOG_TYPE => array(
'name' => 'Blog type',
'type' => 'list',
'title' => 'Select the blog you want to follow',
'values' => array(
self::BLOG_TYPE_HOME => 'blog/',
self::BLOG_TYPE_COMPANIES_HIRING => 'blog/companies-hiring/',
self::BLOG_TYPE_CAREER_ADVICE => 'blog/career-advice/',
self::BLOG_TYPE_INTERVIEWS => 'blog/interviews/',
self::BLOG_TYPE_GUIDE => 'blog/guide/'
)
),
self::PARAM_BLOG_FULL => array(
'name' => 'Full article',
'type' => 'checkbox',
'title' => 'Enable to return the full article for each post'
),
),
self::CONTEXT_REVIEW => array(
self::PARAM_REVIEW_COMPANY => array(
'name' => 'Company URL',
'type' => 'text',
'required' => true,
'title' => 'Paste the company review page URL here!',
'exampleValue' => 'https://www.glassdoor.com/Reviews/GitHub-Reviews-E671945.htm'
)
),
self::CONTEXT_GLOBAL => array(
self::PARAM_LIMIT => array(
'name' => 'Limit',
'type' => 'number',
'defaultValue' => -1,
'title' => 'Specifies the maximum number of items to return (default: All)'
)
)
);
private $host = self::URI; // They redirect without notice :/
private $title = '';
public function getURI() {
switch($this->queriedContext) {
case self::CONTEXT_BLOG:
return self::URI . $this->getInput(self::PARAM_BLOG_TYPE);
case self::CONTEXT_REVIEW:
return $this->filterCompanyURI($this->getInput(self::PARAM_REVIEW_COMPANY));
}
return parent::getURI();
}
public function getName() {
return $this->title ? $this->title . ' - ' . self::NAME : parent::getName();
}
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Failed loading contents!');
$this->host = $html->find('link[rel="canonical"]', 0)->href;
$html = defaultLinkTo($html, $this->host);
$this->title = $html->find('meta[property="og:title"]', 0)->content;
$limit = $this->getInput(self::PARAM_LIMIT);
switch($this->queriedContext) {
case self::CONTEXT_BLOG:
$this->collectBlogData($html, $limit);
break;
case self::CONTEXT_REVIEW:
$this->collectReviewData($html, $limit);
break;
}
}
private function collectBlogData($html, $limit) {
$posts = $html->find('section')
or returnServerError('Unable to find blog posts!');
foreach($posts as $post) {
$item = array();
$item['uri'] = $post->find('header a', 0)->href;
$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'})
);
// optionally load full articles
if($this->getInput(self::PARAM_BLOG_FULL)) {
$full_html = getSimpleHTMLDOMCached($item['uri'])
or returnServerError('Unable to load full article!');
$full_html = defaultLinkTo($full_html, $this->host);
$item['author'] = $full_html->find('a[rel="author"]', 0);
$item['content'] = $full_html->find('article', 0);
$item['timestamp'] = strtotime($full_html->find('time.updated', 0)->datetime);
$item['categories'] = $full_html->find('span[class="post_tag"]');
}
$this->items[] = $item;
if($limit > 0 && count($this->items) >= $limit)
return;
}
}
private function collectReviewData($html, $limit) {
$reviews = $html->find('#EmployerReviews li[id^="empReview]')
or returnServerError('Unable to find reviews!');
foreach($reviews as $review) {
$item = array();
$item['uri'] = $review->find('a.reviewLink', 0)->href;
$item['title'] = $review->find('[class="summary"]', 0)->plaintext;
$item['author'] = $review->find('div.author span', 0)->plaintext;
$item['timestamp'] = strtotime($review->find('time', 0)->datetime);
$mainText = $review->find('p.mainText', 0)->plaintext;
$description = $review->find('div.prosConsAdvice', 0)->innertext;
$item['content'] = "<p>{$mainText}</p><p>{$description}</p>";
$this->items[] = $item;
if($limit > 0 && count($this->items) >= $limit)
return;
}
}
private function getFullSizeImageURI($uri) {
/* Images are scaled for display on the website. The scaling takes place
* on the host, who provides images in different sizes.
*
* For example:
* https://www.glassdoor.com/blog/app/uploads/sites/2/GettyImages-982402074-e1538092065712-390x193.jpg
*
* By removing the size information we receive the full sized image.
*
* For example:
* https://www.glassdoor.com/blog/app/uploads/sites/2/GettyImages-982402074-e1538092065712.jpg
*/
$uri = filter_var($uri, FILTER_SANITIZE_URL);
return preg_replace('/(.*)(\-\d+x\d+)(\.jpg)/', '$1$3', $uri);
}
private function filterCompanyURI($uri) {
/* Make sure the URI is a valid review page. Unfortunately there is no
* simple way to determine if the URI is valid, because of automagic
* redirection and strange naming conventions.
*/
if(!filter_var($uri,
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
returnClientError('The specified URL is invalid!');
}
$uri = filter_var($uri, FILTER_SANITIZE_URL);
$path = parse_url($uri, PHP_URL_PATH);
$parts = explode('/', $path);
$allowed_strings = array(
'de-DE' => 'Bewertungen',
'en-AU' => 'Reviews',
'nl-BE' => 'Reviews',
'fr-BE' => 'Avis',
'en-CA' => 'Reviews',
'fr-CA' => 'Avis',
'fr-FR' => 'Avis',
'en-IN' => 'Reviews',
'en-IE' => 'Reviews',
'nl-NL' => 'Reviews',
'de-AT' => 'Bewertungen',
'de-CH' => 'Bewertungen',
'fr-CH' => 'Avis',
'en-GB' => 'Reviews',
'en' => 'Reviews'
);
if(!in_array($parts[1], $allowed_strings)) {
returnClientError('Please specify a URL pointing to the companies review page!');
}
return $uri;
}
}

View File

@@ -22,6 +22,10 @@ class GooglePlusPostBridge extends BridgeAbstract{
)
));
public function getIcon() {
return 'https://ssl.gstatic.com/images/branding/product/ico/google_plus_alldp.ico';
}
public function collectData(){
$username = $this->getInput('username');
@@ -74,7 +78,9 @@ class GooglePlusPostBridge extends BridgeAbstract{
// Make title at least 50 characters long, but don't add '...' if it is shorter!
if(strlen($message->plaintext) > 50) {
$end = strpos($message->plaintext, ' ', 50);
$end = strpos($message->plaintext, ' ', 50) ?: strlen($message->plaintext);
} else {
$end = strlen($message->plaintext);
}
if(strlen(substr($message->plaintext, 0, $end)) === strlen($message->plaintext)) {
@@ -199,5 +205,4 @@ class GooglePlusPostBridge extends BridgeAbstract{
return implode('', [$scheme, $user, $pass, $host, $port, $path, $query, $fragment]);
}
}

View File

@@ -28,7 +28,7 @@ class GoogleSearchBridge extends BridgeAbstract {
$html = getSimpleHTMLDOM(self::URI
. 'search?q='
. urlencode($this->getInput('q'))
.'&num=100&complete=0&tbs=qdr:y,sbd:1')
. '&num=100&complete=0&tbs=qdr:y,sbd:1')
or returnServerError('No results for this query.');
$emIsRes = $html->find('div[id=ires]', 0);

View File

@@ -49,6 +49,7 @@ class GrandComicsDatabaseBridge extends BridgeAbstract {
}
// Build final item
$content = str_replace('href="/', 'href="' . static::URI, $content);
$item = array();
$item['title'] = $seriesName . ' - ' . $key_date;
$item['timestamp'] = strtotime($key_date);

View File

@@ -17,27 +17,25 @@ class HotUKDealsBridge extends PepperBridgeAbstract {
'hide_expired' => array(
'name' => 'Hide expired deals',
'type' => 'checkbox',
'required' => 'true'
'required' => true
),
'hide_local' => array(
'name' => 'Hide local deals',
'type' => 'checkbox',
'title' => 'Hide deals in physical store',
'required' => 'true'
'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 +43,7 @@ class HotUKDealsBridge extends PepperBridgeAbstract {
'group' => array(
'name' => 'Group',
'type' => 'list',
'required' => 'true',
'required' => true,
'title' => 'Group whose deals must be displayed',
'values' => array(
'2DS' => '2ds',
@@ -1319,7 +1317,7 @@ class HotUKDealsBridge extends PepperBridgeAbstract {
'order' => array(
'name' => 'Order by',
'type' => 'list',
'required' => 'true',
'required' => true,
'title' => 'Sort order of deals',
'values' => array(
'From the most to the least hot deal' => '-hot',

View File

@@ -3,22 +3,28 @@ class InstagramBridge extends BridgeAbstract {
const MAINTAINER = 'pauder';
const NAME = 'Instagram Bridge';
const URI = 'https://instagram.com/';
const URI = 'https://www.instagram.com/';
const DESCRIPTION = 'Returns the newest images';
const PARAMETERS = array(
array(
'Username' => array(
'u' => array(
'name' => 'username',
'required' => true
)
),
array(
'Hashtag' => array(
'h' => array(
'name' => 'hashtag',
'required' => true
)
),
'Location' => array(
'l' => array(
'name' => 'location',
'required' => true
)
),
'global' => array(
'media_type' => array(
'name' => 'Media type',
@@ -38,16 +44,18 @@ class InstagramBridge extends BridgeAbstract {
public function collectData(){
if(!is_null($this->getInput('h')) && $this->getInput('media_type') == 'story') {
returnClientError('Stories are not supported for hashtags!');
if(is_null($this->getInput('u')) && $this->getInput('media_type') == 'story') {
returnClientError('Stories are not supported for hashtags nor locations!');
}
$data = $this->getInstagramJSON($this->getURI());
if(!is_null($this->getInput('u'))) {
$userMedia = $data->entry_data->ProfilePage[0]->graphql->user->edge_owner_to_timeline_media->edges;
} else {
} elseif(!is_null($this->getInput('h'))) {
$userMedia = $data->entry_data->TagPage[0]->graphql->hashtag->edge_hashtag_to_media->edges;
} elseif(!is_null($this->getInput('l'))) {
$userMedia = $data->entry_data->LocationsPage[0]->graphql->location->edge_location_to_media->edges;
}
foreach($userMedia as $media) {
@@ -74,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') {
@@ -85,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);
}
@@ -101,16 +121,21 @@ class InstagramBridge extends BridgeAbstract {
$mediaInfo = $data->entry_data->PostPage[0]->graphql->shortcode_media;
//Process the first element, that isn't in the node graph
$caption = $mediaInfo->edge_media_to_caption->edges[0]->node->text;
if (count($mediaInfo->edge_media_to_caption->edges) > 0) {
$caption = $mediaInfo->edge_media_to_caption->edges[0]->node->text;
} else {
$caption = '';
}
$enclosures = [$mediaInfo->display_url];
$content = '<img src="' . htmlentities($mediaInfo->display_url) . '" alt="'. $caption . '" />';
$content = '<img src="' . htmlentities($mediaInfo->display_url) . '" alt="' . $caption . '" />';
foreach($mediaInfo->edge_sidecar_to_children->edges as $media) {
$content .= '<img src="' . htmlentities($media->node->display_url) . '" alt="'. $caption . '" />';
$enclosures[] = $media->node->display_url;
$display_url = $media->node->display_url;
if(!in_array($display_url, $enclosures)) { // add only if not added yet
$content .= '<img src="' . htmlentities($display_url) . '" alt="' . $caption . '" />';
$enclosures[] = $display_url;
}
}
return [$content, $enclosures];
@@ -139,11 +164,12 @@ class InstagramBridge extends BridgeAbstract {
public function getURI(){
if(!is_null($this->getInput('u'))) {
return self::URI . urlencode($this->getInput('u'));
return self::URI . urlencode($this->getInput('u')) . '/';
} elseif(!is_null($this->getInput('h'))) {
return self::URI . 'explore/tags/' . urlencode($this->getInput('h'));
} elseif(!is_null($this->getInput('l'))) {
return self::URI . 'explore/locations/' . urlencode($this->getInput('l'));
}
return parent::getURI();
}
}

View File

@@ -3,7 +3,7 @@ class JapanExpoBridge extends BridgeAbstract {
const MAINTAINER = 'Ginko';
const NAME = 'Japan Expo Actualités';
const URI = 'http://www.japan-expo-paris.com/fr/actualites';
const URI = 'https://www.japan-expo-paris.com/fr/actualites';
const CACHE_TIMEOUT = 14400; // 4h
const DESCRIPTION = 'Returns most recent entries from Japan Expo actualités.';
const PARAMETERS = array( array(
@@ -13,6 +13,10 @@ class JapanExpoBridge extends BridgeAbstract {
)
));
public function getIcon() {
return 'https://s.japan-expo.com/katana/images/JES073/favicons/paris.png';
}
public function collectData(){
function frenchPubDateToTimestamp($date_to_parse) {
@@ -51,7 +55,7 @@ class JapanExpoBridge extends BridgeAbstract {
foreach($html->find('a._tile2') as $element) {
$url = $element->href;
$thumbnail = 'http://s.japan-expo.com/katana/images/JES049/paris.png';
$thumbnail = 'https://s.japan-expo.com/katana/images/JES049/paris.png';
preg_match('/url\(([^)]+)\)/', $element->find('img.rspvimgset', 0)->style, $img_search_result);
if(count($img_search_result) >= 2)
@@ -62,7 +66,8 @@ class JapanExpoBridge extends BridgeAbstract {
break;
}
$article_html = getSimpleHTMLDOMCached('Could not request JapanExpo: ' . $url);
$article_html = getSimpleHTMLDOMCached($url)
or returnServerError('Could not request JapanExpo: ' . $url);
$header = $article_html->find('header.pageHeadBox', 0);
$timestamp = strtotime($header->find('time', 0)->datetime);
$title_html = $header->find('div.section', 0)->next_sibling();
@@ -92,6 +97,7 @@ class JapanExpoBridge extends BridgeAbstract {
$item['uri'] = $url;
$item['title'] = $title;
$item['timestamp'] = $timestamp;
$item['enclosures'] = array($thumbnail);
$item['content'] = $content;
$this->items[] = $item;
$count++;

View File

@@ -132,7 +132,7 @@ class JustETFBridge extends BridgeAbstract {
date_time_set($df, 0, 0);
// debugMessage(date_format($df, 'U'));
// Debug::log(date_format($df, 'U'));
return date_format($df, 'U');
}
@@ -210,7 +210,7 @@ class JustETFBridge extends BridgeAbstract {
$element = $article->find('div.subheadline', 0)
or returnServerError('Date not found!');
// debugMessage($element->plaintext);
// Debug::log($element->plaintext);
$date = trim(explode('|', $element->plaintext)[0]);
@@ -223,7 +223,7 @@ class JustETFBridge extends BridgeAbstract {
$element->find('a', 0)->onclick = '';
// debugMessage($element->innertext);
// Debug::log($element->innertext);
return $element->innertext;
}
@@ -288,7 +288,7 @@ class JustETFBridge extends BridgeAbstract {
$element = $html->find('div.infobox div.vallabel', 0)
or returnServerError('Date not found!');
// debugMessage($element->plaintext);
// Debug::log($element->plaintext);
$date = trim(explode("\r\n", $element->plaintext)[1]);
@@ -348,6 +348,5 @@ class JustETFBridge extends BridgeAbstract {
return $element->plaintext;
}
#endregion
}

View File

@@ -36,6 +36,11 @@ class KATBridge extends BridgeAbstract {
'name' => 'Only get results from Elite or Verified uploaders ?',
),
));
public function getIcon() {
return 'https://statuskatcrco-631f.kxcdn.com/assets/images/favicon.ico';
}
public function collectData(){
function parseDateTimestamp($element){
$guessedDate = strptime($element, '%d-%m-%Y %H:%M:%S');
@@ -48,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

@@ -38,6 +38,10 @@ class KernelBugTrackerBridge extends BridgeAbstract {
private $bugid = '';
private $bugdesc = '';
public function getIcon() {
return self::URI . '/images/favicon.ico';
}
public function collectData(){
$limit = $this->getInput('limit');
$sorting = $this->getInput('sorting');
@@ -146,5 +150,4 @@ class KernelBugTrackerBridge extends BridgeAbstract {
return $html;
}
}

View File

@@ -64,7 +64,7 @@ class KununuBridge extends BridgeAbstract {
return parent::getURI();
}
function getName(){
public function getName(){
if(!is_null($this->getInput('company'))) {
$company = $this->fixCompanyName($this->getInput('company'));
return ($this->companyName ?: $company) . ' - ' . self::NAME;
@@ -73,52 +73,67 @@ class KununuBridge extends BridgeAbstract {
return parent::getName();
}
public function getIcon() {
return 'https://www.kununu.com/favicon-196x196.png';
}
public function collectData(){
$full = $this->getInput('full');
// Load page
$html = getSimpleHTMLDOMCached($this->getURI());
if(!$html)
returnServerError('Unable to receive data from ' . $this->getURI() . '!');
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Unable to receive data from ' . $this->getURI() . '!');
$html = defaultLinkTo($html, static::URI);
// Update name for this request
$this->companyName = $this->extractCompanyName($html);
$company = $html->find('span[class="company-name"]', 0)
or returnServerError('Cannot find company name!');
$this->companyName = $company->innertext;
// Find the section with all the panels (reviews)
$section = $html->find('section.kununu-scroll-element', 0);
if($section === false)
returnServerError('Unable to find panel section!');
$section = $html->find('section.kununu-scroll-element', 0)
or returnServerError('Unable to find panel section!');
// Find all articles (within the panels)
$articles = $section->find('article');
if($articles === false || empty($articles))
returnServerError('Unable to find articles!');
$articles = $section->find('article')
or returnServerError('Unable to find articles!');
// Go through all articles
foreach($articles as $article) {
$anchor = $article->find('h1.review-title a', 0)
or returnServerError('Cannot find article URI!');
$date = $article->find('meta[itemprop=dateCreated]', 0)
or returnServerError('Cannot find article date!');
$rating = $article->find('span.rating', 0)
or returnServerError('Cannot find article rating!');
$summary = $article->find('[itemprop=name]', 0)
or returnServerError('Cannot find article summary!');
$item = array();
$item['author'] = $this->extractArticleAuthorPosition($article);
$item['timestamp'] = $this->extractArticleDate($article);
$item['title'] = $this->extractArticleRating($article)
$item['timestamp'] = strtotime($date);
$item['title'] = $rating->getAttribute('aria-label')
. ' : '
. $this->extractArticleSummary($article);
. strip_tags($summary->innertext);
$item['uri'] = $this->extractArticleUri($article);
$item['uri'] = $anchor->href;
if($full)
if($full) {
$item['content'] = $this->extractFullDescription($item['uri']);
else
} else {
$item['content'] = $this->extractArticleDescription($article);
}
$this->items[] = $item;
}
}
/**
* Fixes relative URLs in the given text
*/
private function fixUrl($text){
return preg_replace('/href=(\'|\")\//i', 'href="'.self::URI, $text);
}
}
/*
@@ -128,73 +143,11 @@ class KununuBridge extends BridgeAbstract {
$company = trim($company);
$company = str_replace(' ', '-', $company);
$company = strtolower($company);
return $this->encodeUmlauts($company);
}
/**
* Encodes unmlauts in the given text
*/
private function encodeUmlauts($text){
$umlauts = Array('/ä/','/ö/','/ü/','/Ä/','/Ö/','/Ü/','/ß/');
$replace = Array('ae','oe','ue','Ae','Oe','Ue','ss');
return preg_replace($umlauts, $replace, $text);
}
/**
* Returns the company name from the review html
*/
private function extractCompanyName($html){
$company_name = $html->find('h1[itemprop=name]', 0);
if(is_null($company_name))
returnServerError('Cannot find company name!');
return $company_name->plaintext;
}
/**
* Returns the date from a given article
*/
private function extractArticleDate($article){
// They conviniently provide a time attribute for us :)
$date = $article->find('meta[itemprop=dateCreated]', 0);
if(is_null($date))
returnServerError('Cannot find article date!');
return strtotime($date->content);
}
/**
* Returns the rating from a given article
*/
private function extractArticleRating($article){
$rating = $article->find('span.rating', 0);
if(is_null($rating))
returnServerError('Cannot find article rating!');
return $rating->getAttribute('aria-label');
}
/**
* Returns the summary from a given article
*/
private function extractArticleSummary($article){
$summary = $article->find('[itemprop=name]', 0);
if(is_null($summary))
returnServerError('Cannot find article summary!');
return strip_tags($summary->innertext);
}
/**
* Returns the URI from a given article
*/
private function extractArticleUri($article){
$anchor = $article->find('h1.review-title a', 0);
if(is_null($anchor))
returnServerError('Cannot find article URI!');
return self::URI . $anchor->href;
return preg_replace($umlauts, $replace, $company);
}
/**
@@ -202,9 +155,8 @@ class KununuBridge extends BridgeAbstract {
*/
private function extractArticleAuthorPosition($article){
// We need to parse the user-content manually
$user_content = $article->find('div.user-content', 0);
if(is_null($user_content))
returnServerError('Cannot find user content!');
$user_content = $article->find('div.user-content', 0)
or returnServerError('Cannot find user content!');
// Go through all h2 elements to find index of required span (I know... it's stupid)
$author_position = 'Unknown';
@@ -222,11 +174,10 @@ class KununuBridge extends BridgeAbstract {
* Returns the description from a given article
*/
private function extractArticleDescription($article){
$description = $article->find('[itemprop=reviewBody]', 0);
if(is_null($description))
returnServerError('Cannot find article description!');
$description = $article->find('[itemprop=reviewBody]', 0)
or returnServerError('Cannot find article description!');
return $this->fixUrl($description->innertext);
return $description->innertext;
}
/**
@@ -234,14 +185,14 @@ class KununuBridge extends BridgeAbstract {
*/
private function extractFullDescription($uri){
// Load full article
$html = getSimpleHTMLDOMCached($uri);
if($html === false)
returnServerError('Could not load full description!');
$html = getSimpleHTMLDOMCached($uri)
or returnServerError('Could not load full description!');
$html = defaultLinkTo($html, static::URI);
// Find the article
$article = $html->find('article', 0);
if(is_null($article))
returnServerError('Cannot find article!');
$article = $html->find('article', 0)
or returnServerError('Cannot find article!');
// Luckily they use the same layout for the review overview and full article pages :)
return $this->extractArticleDescription($article);

View File

@@ -9,7 +9,7 @@ class LWNprevBridge extends BridgeAbstract{
private $editionTimeStamp;
function getURI(){
return self::URI.'free/bigpage';
return self::URI . 'free/bigpage';
}
private function jumpToNextTag(&$node){
@@ -47,7 +47,7 @@ class LWNprevBridge extends BridgeAbstract{
<html><head><title>LWN</title></head><body>{$content}</body></html>
EOD;
} else {
$content = $content.'</body></html>';
$content = $content . '</body></html>';
}
libxml_use_internal_errors(true);
@@ -172,7 +172,7 @@ EOD;
$prefix = '';
if(!empty($cats[0])) {
$prefix .= '['.$cats[0].($cats[1] ? '/'.$cats[1] : '').'] ';
$prefix .= '[' . $cats[0] . ($cats[1] ? '/' . $cats[1] : '') . '] ';
}
return $prefix;
}
@@ -188,7 +188,7 @@ EOD;
$item = array();
$item['uri'] = self::URI.'#'.count($items);
$item['uri'] = self::URI . '#' . count($items);
$item['timestamp'] = $this->editionTimeStamp;
@@ -197,7 +197,7 @@ EOD;
$cat = $newsletters->previousSibling;
$this->jumpToPreviousTag($cat);
$prefix = $this->getItemPrefix($cat, $cats);
$item['title'] = $prefix.' '.$newsletters->textContent;
$item['title'] = $prefix . ' ' . $newsletters->textContent;
$node = $newsletters;
$content = '';
@@ -233,7 +233,7 @@ EOD;
$cat = $cat->previousSibling;
$this->jumpToPreviousTag($cat);
$prefix = $this->getItemPrefix($cat, $cats);
$item['title'] = $prefix.' '.$title->textContent;
$item['title'] = $prefix . ' ' . $title->textContent;
$items[] = array_merge($item, $this->getArticleContent($title));
}
@@ -255,7 +255,7 @@ EOD;
$cat = $cat->previousSibling;
$this->jumpToPreviousTag($cat);
$prefix = $this->getItemPrefix($cat, $cats);
$item['title'] = $prefix.' '.$title->textContent;
$item['title'] = $prefix . ' ' . $title->textContent;
$items[] = array_merge($item, $this->getArticleContent($title));
}

View File

@@ -8,8 +8,8 @@ class LeBonCoinBridge extends BridgeAbstract {
const PARAMETERS = array(
array(
'k' => array('name' => 'Mot Clé'),
'r' => array(
'keywords' => array('name' => 'Mots-Clés'),
'region' => array(
'name' => 'Région',
'type' => 'list',
'values' => array(
@@ -42,8 +42,114 @@ class LeBonCoinBridge extends BridgeAbstract {
'Réunion' => '26'
)
),
'cities' => array('name' => 'Ville'),
'c' => array(
'department' => array(
'name' => 'Département',
'type' => 'list',
'values' => array(
'' => '',
'Ain' => '1',
'Aisne' => '2',
'Allier' => '3',
'Alpes-de-Haute-Provence' => '4',
'Hautes-Alpes' => '5',
'Alpes-Maritimes' => '6',
'Ardèche' => '7',
'Ardennes' => '8',
'Ariège' => '9',
'Aube' => '10',
'Aude' => '11',
'Aveyron' => '12',
'Bouches-du-Rhône' => '13',
'Calvados' => '14',
'Cantal' => '15',
'Charente' => '16',
'Charente-Maritime' => '17',
'Cher' => '18',
'Corrèze' => '19',
'Corse-du-Sud' => '2A',
'Haute-Corse' => '2B',
'Côte-d\'Or' => '21',
'Côtes-d\'Armor' => '22',
'Creuse' => '23',
'Dordogne' => '24',
'Doubs' => '25',
'Drôme' => '26',
'Eure' => '27',
'Eure-et-Loir' => '28',
'Finistère' => '29',
'Gard' => '30',
'Haute-Garonne' => '31',
'Gers' => '32',
'Gironde' => '33',
'Hérault' => '34',
'Ille-et-Vilaine' => '35',
'Indre' => '36',
'Indre-et-Loire' => '37',
'Isère' => '38',
'Jura' => '39',
'Landes' => '40',
'Loir-et-Cher' => '41',
'Loire' => '42',
'Haute-Loire' => '43',
'Loire-Atlantique' => '44',
'Loiret' => '45',
'Lot' => '46',
'Lot-et-Garonne' => '47',
'Lozère' => '48',
'Maine-et-Loire' => '49',
'Manche' => '50',
'Marne' => '51',
'Haute-Marne' => '52',
'Mayenne' => '53',
'Meurthe-et-Moselle' => '54',
'Meuse' => '55',
'Morbihan' => '56',
'Moselle' => '57',
'Nièvre' => '58',
'Nord' => '59',
'Oise' => '60',
'Orne' => '61',
'Pas-de-Calais' => '62',
'Puy-de-Dôme' => '63',
'Pyrénées-Atlantiques' => '64',
'Hautes-Pyrénées' => '65',
'Pyrénées-Orientales' => '66',
'Bas-Rhin' => '67',
'Haut-Rhin' => '68',
'Rhône' => '69',
'Haute-Saône' => '70',
'Saône-et-Loire' => '71',
'Sarthe' => '72',
'Savoie' => '73',
'Haute-Savoie' => '74',
'Paris' => '75',
'Seine-Maritime' => '76',
'Seine-et-Marne' => '77',
'Yvelines' => '78',
'Deux-Sèvres' => '79',
'Somme' => '80',
'Tarn' => '81',
'Tarn-et-Garonne' => '82',
'Var' => '83',
'Vaucluse' => '84',
'Vendée' => '85',
'Vienne' => '86',
'Haute-Vienne' => '87',
'Vosges' => '88',
'Yonne' => '89',
'Territoire de Belfort' => '90',
'Essonne' => '91',
'Hauts-de-Seine' => '92',
'Seine-Saint-Denis' => '93',
'Val-de-Marne' => '94',
'Val-d\'Oise' => '95'
)
),
'cities' => array(
'name' => 'Villes',
'title' => 'Codes postaux séparés par des virgules'
),
'category' => array(
'name' => 'Catégorie',
'type' => 'list',
'values' => array(
@@ -52,7 +158,7 @@ class LeBonCoinBridge extends BridgeAbstract {
'Emploi et recrutement' => '71',
'Offres d\'emploi et jobs' => '33'
),
'VEHICULES' => array(
'VÉHICULES' => array(
'Tous' => '1',
'Voitures' => '2',
'Motos' => '3',
@@ -79,7 +185,7 @@ class LeBonCoinBridge extends BridgeAbstract {
'Hôtels' => '69',
'Hébergements insolites' => '70'
),
'MULTIMEDIA' => array(
'MULTIMÉDIA' => array(
'Tous' => '14',
'Informatique' => '15',
'Consoles & Jeux vidéo' => '43',
@@ -99,7 +205,7 @@ class LeBonCoinBridge extends BridgeAbstract {
'Jeux & Jouets' => '41',
'Vins & Gastronomie' => '48'
),
'MATERIEL PROFESSIONNEL' => array(
'MATÉRIEL PROFESSIONNEL' => array(
'Tous' => '56',
'Matériel Agricole' => '57',
'Transport - Manutention' => '58',
@@ -115,14 +221,14 @@ class LeBonCoinBridge extends BridgeAbstract {
'Tous' => '31',
'Prestations de services' => '34',
'Billetterie' => '35',
'Evénements' => '49',
'Événements' => '49',
'Cours particuliers' => '36',
'Covoiturage' => '65'
),
'MAISON' => array(
'Tous' => '18',
'Ameublement' => '19',
'Electroménager' => '20',
'Électroménager' => '20',
'Arts de la table' => '45',
'Décoration' => '39',
'Linge de maison' => '46',
@@ -132,54 +238,145 @@ class LeBonCoinBridge extends BridgeAbstract {
'Chaussures' => '53',
'Accessoires & Bagagerie' => '47',
'Montres & Bijoux' => '42',
'Equipement bébé' => '23',
'Équipement bébé' => '23',
'Vêtements bébé' => '54',
),
'AUTRES' => '37'
)
),
'o' => array(
'pricemin' => array(
'name' => 'Prix min',
'type' => 'number'
),
'pricemax' => array(
'name' => 'Prix max',
'type' => 'number'
),
'estate' => array(
'name' => 'Type de bien',
'type' => 'list',
'values' => array(
'' => '',
'Maison' => '1',
'Appartement' => '2',
'Terrain' => '3',
'Parking' => '4',
'Autre' => '5'
)
),
'roomsmin' => array(
'name' => 'Pièces min',
'type' => 'number'
),
'roomsmax' => array(
'name' => 'Pièces max',
'type' => 'number'
),
'squaremin' => array(
'name' => 'Surface min',
'type' => 'number'
),
'squaremax' => array(
'name' => 'Surface max',
'type' => 'number'
),
'mileagemin' => array(
'name' => 'Kilométrage min',
'type' => 'number'
),
'mileagemax' => array(
'name' => 'Kilométrage max',
'type' => 'number'
),
'yearmin' => array(
'name' => 'Année min',
'type' => 'number'
),
'yearmax' => array(
'name' => 'Année max',
'type' => 'number'
),
'cubiccapacitymin' => array(
'name' => 'Cylindrée min',
'type' => 'number'
),
'cubiccapacitymax' => array(
'name' => 'Cylindrée max',
'type' => 'number'
),
'fuel' => array(
'name' => 'Énergie',
'type' => 'list',
'values' => array(
'' => '',
'Essence' => '1',
'Diesel' => '2',
'GPL' => '3',
'Électrique' => '4',
'Hybride' => '6',
'Autre' => '5'
)
),
'owner' => array(
'name' => 'Vendeur',
'type' => 'list',
'values' => array(
'Tous' => '',
'Particuliers' => 'private',
'Professionnels' => 'pro',
'Professionnels' => 'pro'
)
)
)
);
public function collectData(){
public static $LBC_API_KEY = 'ba0c2dad52b3ec';
$params = array(
'text' => $this->getInput('k'),
'region' => $this->getInput('r'),
'cities' => $this->getInput('cities'),
'category' => $this->getInput('c'),
'owner_type' => $this->getInput('o'),
);
private function getRange($field, $range_min, $range_max){
$url = self::URI . 'recherche/?' . http_build_query($params);
$html = getContents($url)
or returnServerError('Could not request LeBonCoin. Tried: ' . $url);
if(!preg_match('/^<script>window.FLUX_STATE[^\r\n]*/m', $html, $matches)) {
returnServerError('Could not parse JSON in page content.');
if(!is_null($range_min)
&& !is_null($range_max)
&& $range_min > $range_max) {
returnClientError('Min-' . $field . ' must be lower than max-' . $field . '.');
}
$clean_match = str_replace(
array('</script>', '<script>window.FLUX_STATE = '),
array('', ''),
$matches[0]
);
$json = json_decode($clean_match);
if(!is_null($range_min)
&& is_null($range_max)) {
returnClientError('Max-' . $field . ' is needed when min-' . $field . ' is setted (range).');
}
if($json->adSearch->data->total === 0) {
return array(
'min' => $range_min,
'max' => $range_max
);
}
public function collectData(){
$url = 'https://api.leboncoin.fr/finder/search/';
$data = $this->buildRequestJson();
$header = array(
'Content-Type: application/json',
'Content-Length: ' . strlen($data),
'api_key: ' . self::$LBC_API_KEY
);
$opts = array(
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $data
);
$content = getContents($url, $header, $opts)
or returnServerError('Could not request LeBonCoin. Tried: ' . $url);
$json = json_decode($content);
if($json->total === 0) {
return;
}
foreach($json->adSearch->data->ads as $element) {
foreach($json->ads as $element) {
$item['title'] = $element->subject;
$item['content'] = $element->body;
@@ -221,4 +418,119 @@ class LeBonCoinBridge extends BridgeAbstract {
$this->items[] = $item;
}
}
private function buildRequestJson() {
$requestJson = new StdClass();
$requestJson->owner_type = $this->getInput('owner');
$requestJson->filters = new StdClass();
$requestJson->filters->keywords = array(
'text' => $this->getInput('keywords')
);
if($this->getInput('region') != '') {
$requestJson->filters->location['regions'] = [$this->getInput('region')];
}
if($this->getInput('department') != '') {
$requestJson->filters->location['departments'] = [$this->getInput('department')];
}
if($this->getInput('cities') != '') {
$requestJson->filters->location['city_zipcodes'] = array();
foreach (explode(',', $this->getInput('cities')) as $zipcode) {
$requestJson->filters->location['city_zipcodes'][] = array(
'zipcode' => trim($zipcode)
);
}
}
$requestJson->filters->category = array(
'id' => $this->getInput('category')
);
if($this->getInput('pricemin') != ''
|| $this->getInput('pricemax') != '') {
$requestJson->filters->ranges->price = $this->getRange(
'price',
$this->getInput('pricemin'),
$this->getInput('pricemax')
);
}
if($this->getInput('estate') != '') {
$requestJson->filters->enums['real_estate_type'] = [$this->getInput('estate')];
}
if($this->getInput('roomsmin') != ''
|| $this->getInput('roomsmax') != '') {
$requestJson->filters->ranges->rooms = $this->getRange(
'rooms',
$this->getInput('roomsmin'),
$this->getInput('roomsmax')
);
}
if($this->getInput('squaremin') != ''
|| $this->getInput('squaremax') != '') {
$requestJson->filters->ranges->square = $this->getRange(
'square',
$this->getInput('squaremin'),
$this->getInput('squaremax')
);
}
if($this->getInput('mileagemin') != ''
|| $this->getInput('mileagemax') != '') {
$requestJson->filters->ranges->mileage = $this->getRange(
'mileage',
$this->getInput('mileagemin'),
$this->getInput('mileagemax')
);
}
if($this->getInput('yearmin') != ''
|| $this->getInput('yearmax') != '') {
$requestJson->filters->ranges->regdate = $this->getRange(
'year',
$this->getInput('yearmin'),
$this->getInput('yearmax')
);
}
if($this->getInput('cubiccapacitymin') != ''
|| $this->getInput('cubiccapacitymax') != '') {
$requestJson->filters->ranges->cubic_capacity = $this->getRange(
'cubic_capacity',
$this->getInput('cubiccapacitymin'),
$this->getInput('cubiccapacitymax')
);
}
if($this->getInput('fuel') != '') {
$requestJson->filters->enums['fuel'] = [$this->getInput('fuel')];
}
$requestJson->limit = 30;
return json_encode($requestJson);
}
}

View File

@@ -3,8 +3,7 @@ class LeMondeInformatiqueBridge extends FeedExpander {
const MAINTAINER = 'ORelio';
const NAME = 'Le Monde Informatique';
const URI = 'http://www.lemondeinformatique.fr/';
const CACHE_TIMEOUT = 1800; // 30min
const URI = 'https://www.lemondeinformatique.fr/';
const DESCRIPTION = 'Returns the newest articles.';
public function collectData(){
@@ -15,30 +14,26 @@ class LeMondeInformatiqueBridge extends FeedExpander {
$item = parent::parseItem($newsItem);
$article_html = getSimpleHTMLDOMCached($item['uri'])
or returnServerError('Could not request LeMondeInformatique: ' . $item['uri']);
$item['content'] = $this->cleanArticle($article_html->find('div#article', 0)->innertext);
$item['title'] = $article_html->find('h1.cleanprint-title', 0)->plaintext;
//Deduce thumbnail URL from article image URL
$item['enclosures'] = array(
str_replace(
'/grande/',
'/petite/',
$article_html->find('.article-image', 0)->find('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));
$item['author'] = utf8_encode($article_html->find('div.author-infos', 0)->find('b', 0)->plaintext);
return $item;
}
private function stripCDATA($string){
$string = str_replace('<![CDATA[', '', $string);
$string = str_replace(']]>', '', $string);
return $string;
}
private function stripWithDelimiters($string, $start, $end){
while(strpos($string, $start) !== false) {
$section_to_remove = substr($string, strpos($string, $start));
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
$string = str_replace($section_to_remove, '', $string);
}
return $string;
}
private function cleanArticle($article_html){
$article_html = $this->stripWithDelimiters($article_html, '<script', '</script>');
$article_html = $this->stripWithDelimiters($article_html, '<h1 class="cleanprint-title"', '</h1>');
$article_html = stripWithDelimiters($article_html, '<script', '</script>');
$article_html = explode('<p class="contact-error', $article_html)[0] . '</div>';
return $article_html;
}
}

View File

@@ -38,6 +38,10 @@ class LegifranceJOBridge extends BridgeAbstract {
return $item;
}
public function getIcon() {
return 'https://www.legifrance.gouv.fr/img/favicon.ico';
}
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or $this->returnServer('Unable to download ' . self::URI);

View File

@@ -3,7 +3,7 @@ class LesJoiesDuCodeBridge extends BridgeAbstract {
const MAINTAINER = 'superbaillot.net';
const NAME = 'Les Joies Du Code';
const URI = 'http://lesjoiesducode.fr/';
const URI = 'https://lesjoiesducode.fr/';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'LesJoiesDuCode';
@@ -27,15 +27,7 @@ class LesJoiesDuCodeBridge extends BridgeAbstract {
}
$content = $temp->innertext;
$auteur = $temp->find('i', 0);
$pos = strpos($auteur->innertext, 'by');
if($pos > 0) {
$auteur = trim(str_replace('*/', '', substr($auteur->innertext, ($pos + 2))));
$item['author'] = $auteur;
}
$item['content'] .= trim($content);
$item['content'] = trim($content);
$item['uri'] = $url;
$item['title'] = trim($titre);

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

@@ -0,0 +1,28 @@
<?php
class MozillaSecurityBridge extends BridgeAbstract {
const MAINTAINER = 'm0le.net';
const NAME = 'Mozilla Security Advisories';
const URI = 'https://www.mozilla.org/en-US/security/advisories/';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Mozilla Security Advisories';
const WEBROOT = 'https://www.mozilla.org';
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request MSA.');
$html = defaultLinkTo($html, self::WEBROOT);
$item = array();
$articles = $html->find('div[itemprop="articleBody"] h2');
foreach ($articles as $element) {
$item['title'] = $element->innertext;
$item['timestamp'] = strtotime($element->innertext);
$item['content'] = $element->next_sibling()->innertext;
$item['uri'] = self::URI;
$this->items[] = $item;
}
}
}

View File

@@ -17,27 +17,25 @@ class MydealsBridge extends PepperBridgeAbstract {
'hide_expired' => array(
'name' => 'Abgelaufenes ausblenden',
'type' => 'checkbox',
'required' => 'true'
'required' => true
),
'hide_local' => array(
'name' => 'Lokales ausblenden',
'type' => 'checkbox',
'title' => 'Deals im physischen Geschäft ausblenden',
'required' => 'true'
'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 +43,7 @@ class MydealsBridge extends PepperBridgeAbstract {
'group' => array(
'name' => 'Gruppen',
'type' => 'list',
'required' => 'true',
'required' => true,
'title' => 'Gruppe, deren Deals angezeigt werden müssen',
'values' => array(
'Elektronik' => 'elektronik',
@@ -68,7 +66,7 @@ class MydealsBridge extends PepperBridgeAbstract {
'order' => array(
'name' => 'sortieren nach',
'type' => 'list',
'required' => 'true',
'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

@@ -6,16 +6,6 @@ class NeuviemeArtBridge extends FeedExpander {
const URI = 'http://www.9emeart.fr/';
const DESCRIPTION = 'Returns the newest articles.';
private function stripWithDelimiters($string, $start, $end){
while(strpos($string, $start) !== false) {
$section_to_remove = substr($string, strpos($string, $start));
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
$string = str_replace($section_to_remove, '', $string);
}
return $string;
}
protected function parseItem($item){
$item = parent::parseItem($item);
@@ -34,16 +24,16 @@ class NeuviemeArtBridge extends FeedExpander {
}
$article_content = '';
if($article_image) {
if ($article_image) {
$article_content = '<p><img src="' . $article_image . '" /></p>';
}
$article_content .= str_replace(
'src="/', 'src="' . self::URI,
$article_html->find('div.newsGenerique_con', 0)->innertext
);
$article_content = $this->stripWithDelimiters($article_content, '<script', '</script>');
$article_content = $this->stripWithDelimiters($article_content, '<style', '</style>');
$article_content = $this->stripWithDelimiters($article_content, '<link', '>');
$article_content = stripWithDelimiters($article_content, '<script', '</script>');
$article_content = stripWithDelimiters($article_content, '<style', '</style>');
$article_content = stripWithDelimiters($article_content, '<link', '>');
$item['content'] = $article_content;

View File

@@ -6,29 +6,105 @@ class NextInpactBridge extends FeedExpander {
const URI = 'https://www.nextinpact.com/';
const DESCRIPTION = 'Returns the newest articles.';
const PARAMETERS = array( array(
'feed' => array(
'name' => 'Feed',
'type' => 'list',
'values' => array(
'Tous nos articles' => 'news',
'Nos contenus en accès libre' => 'acces-libre',
'Blog' => 'blog',
'Bons plans' => 'bonsplans'
)
),
'filter_premium' => array(
'name' => 'Premium',
'type' => 'list',
'values' => array(
'No filter' => '0',
'Hide Premium' => '1',
'Only Premium' => '2'
)
),
'filter_brief' => array(
'name' => 'Brief',
'type' => 'list',
'values' => array(
'No filter' => '0',
'Hide Brief' => '1',
'Only Brief' => '2'
)
)
));
public function collectData(){
$this->collectExpandableDatas(self::URI . 'rss/news.xml', 10);
$feed = $this->getInput('feed');
if (empty($feed))
$feed = 'news';
$this->collectExpandableDatas(self::URI . 'rss/' . $feed . '.xml');
}
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
$item['content'] = $this->extractContent($item['uri']);
$item['content'] = $this->extractContent($item, $item['uri']);
if (is_null($item['content']))
return null; //Filtered article
return $item;
}
private function extractContent($url){
$html2 = getSimpleHTMLDOMCached($url);
$text = '<p><em>'
. $html2->find('span.sub_title', 0)->innertext
. '</em></p><p><img src="'
. $html2->find('div.container_main_image_article', 0)->find('img.dedicated', 0)->src
. '" alt="-" /></p><div>'
. $html2->find('div[itemprop=articleBody]', 0)->innertext
. '</div>';
private function extractContent($item, $url){
$html = getSimpleHTMLDOMCached($url);
if (!is_object($html))
return 'Failed to request NextInpact: ' . $url;
foreach(array(
'filter_premium' => 'h2.title_reserve_article',
'filter_brief' => 'div.brief-inner-content'
) as $param_name => $selector) {
$param_val = intval($this->getInput($param_name));
if ($param_val != 0) {
$element_present = is_object($html->find($selector, 0));
$element_wanted = ($param_val == 2);
if ($element_present != $element_wanted) {
return null; //Filter article
}
}
}
if (is_object($html->find('div[itemprop=articleBody], div.brief-inner-content', 0))) {
$subtitle = trim($html->find('span.sub_title, div.brief-head', 0));
if(is_object($subtitle) && $subtitle->plaintext !== $item['title']) {
$subtitle = '<p><em>' . $subtitle->plaintext . '</em></p>';
} else {
$subtitle = '';
}
$postimg = $html->find(
'div.container_main_image_article, div.image-brief-container, div.image-brief-side-container', 0
);
if(is_object($postimg)) {
$postimg = '<p><img src="'
. $postimg->find('img.dedicated', 0)->src
. '" alt="-" /></p>';
} else {
$postimg = '';
}
$text = $subtitle
. $postimg
. $html->find('div[itemprop=articleBody], div.brief-inner-content', 0)->outertext;
} else {
$text = $item['content']
. '<p><em>Failed retrieve full article content</em></p>';
}
$premium_article = $html->find('h2.title_reserve_article', 0);
if (is_object($premium_article)) {
$text .= '<p><em>' . $premium_article->innertext . '</em></p>';
}
$premium_article = $html2->find('h2.title_reserve_article', 0);
if (is_object($premium_article))
$text = $text . '<p><em>' . $premium_article->innertext . '</em></p>';
return $text;
}
}

View File

@@ -32,43 +32,39 @@ class NextgovBridge extends FeedExpander {
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
$item['content'] = '';
$article_thumbnail = 'https://cdn.nextgov.com/nextgov/images/logo.png';
$item['content'] = '<p><b>' . $item['content'] . '</b></p>';
$namespaces = $newsItem->getNamespaces(true);
if(isset($namespaces['media'])) {
$media = $newsItem->children($namespaces['media']);
if(isset($media->content)) {
$attributes = $media->content->attributes();
$item['content'] = '<img src="' . $attributes['url'] . '">';
$item['content'] = '<p><img src="' . $attributes['url'] . '"></p>' . $item['content'];
$article_thumbnail = str_replace(
'large.jpg',
'small.jpg',
strval($attributes['url'])
);
}
}
$item['enclosures'] = array($article_thumbnail);
$item['content'] .= $this->extractContent($item['uri']);
return $item;
}
private function stripWithDelimiters($string, $start, $end){
while (strpos($string, $start) !== false) {
$section_to_remove = substr($string, strpos($string, $start));
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
$string = str_replace($section_to_remove, '', $string);
}
return $string;
}
private function extractContent($url){
$article = getSimpleHTMLDOMCached($url)
or returnServerError('Could not request Nextgov: ' . $url);
$article = getSimpleHTMLDOMCached($url);
$contents = $article->find('div.wysiwyg', 0)->innertext;
$contents = $this->stripWithDelimiters($contents, '<div class="ad-container">', '</div>');
$contents = $this->stripWithDelimiters($contents, '<div', '</div>'); //ad outer div
return $this->stripWithDelimiters($contents, '<script', '</script>');
$contents = ($article_thumbnail == '' ? '' : '<p><img src="' . $article_thumbnail . '" /></p>')
. '<p><b>'
. $article_subtitle
. '</b></p>'
. trim($contents);
if (!is_object($article))
return 'Could not request Nextgov: ' . $url;
$contents = $article->find('div.wysiwyg', 0);
$contents->find('svg.content-tombstone', 0)->outertext = '';
$contents = $contents->innertext;
$contents = stripWithDelimiters($contents, '<div class="ad-container">', '</div>');
$contents = stripWithDelimiters($contents, '<div', '</div>'); //ad outer div
return trim(stripWithDelimiters($contents, '<script', '</script>'));
}
}

331
bridges/NineGagBridge.php Normal file
View File

@@ -0,0 +1,331 @@
<?php
class NineGagBridge extends BridgeAbstract {
const NAME = '9gag Bridge';
const URI = 'https://9gag.com/';
const DESCRIPTION = 'Returns latest quotes from 9gag.';
const MAINTAINER = 'ZeNairolf';
const CACHE_TIMEOUT = 3600;
const PARAMETERS = array(
'Popular' => array(
'd' => array(
'name' => 'Section',
'type' => 'list',
'required' => true,
'values' => array(
'Hot' => 'hot',
'Trending' => 'trending',
'Fresh' => 'fresh',
),
),
'p' => array(
'name' => 'Pages',
'type' => 'number',
'defaultValue' => 3,
),
),
'Sections' => array(
'g' => array(
'name' => 'Section',
'type' => 'list',
'required' => true,
'values' => array(
'Animals' => 'cute',
'Anime & Manga' => 'anime-manga',
'Ask 9GAG' => 'ask9gag',
'Awesome' => 'awesome',
'Basketball' => 'basketball',
'Car' => 'car',
'Classical Art Memes' => 'classicalartmemes',
'Comic' => 'comic',
'Cosplay' => 'cosplay',
'Countryballs' => 'country',
'DIY & Crafts' => 'imadedis',
'Drawing & Illustration' => 'drawing',
'Fan Art' => 'animefanart',
'Food & Drinks' => 'food',
'Football' => 'football',
'Fortnite' => 'fortnite',
'Funny' => 'funny',
'GIF' => 'gif',
'Gaming' => 'gaming',
'Girl' => 'girl',
'Girly Things' => 'girly',
'Guy' => 'guy',
'History' => 'history',
'Home Design' => 'home',
'Horror' => 'horror',
'K-Pop' => 'kpop',
'LEGO' => 'lego',
'League of Legends' => 'leagueoflegends',
'Movie & TV' => 'movie-tv',
'Music' => 'music',
'NFK - Not For Kids' => 'nsfw',
'Overwatch' => 'overwatch',
'PC Master Race' => 'pcmr',
'PUBG' => 'pubg',
'Pic Of The Day' => 'photography',
'Pokémon' => 'pokemon',
'Politics' => 'politics',
'Relationship' => 'relationship',
'Roast Me' => 'roastme',
'Satisfying' => 'satisfying',
'Savage' => 'savage',
'School' => 'school',
'Sci-Tech' => 'science',
'Sport' => 'sport',
'Star Wars' => 'starwars',
'Superhero' => 'superhero',
'Surreal Memes' => 'surrealmemes',
'Timely' => 'timely',
'Travel' => 'travel',
'Video' => 'video',
'WTF' => 'wtf',
'Wallpaper' => 'wallpaper',
'Warhammer' => 'warhammer',
),
),
't' => array(
'name' => 'Type',
'type' => 'list',
'required' => true,
'values' => array(
'Hot' => 'hot',
'Fresh' => 'fresh',
),
),
'p' => array(
'name' => 'Pages',
'type' => 'number',
'defaultValue' => 3,
),
),
);
const MIN_NBR_PAGE = 1;
const MAX_NBR_PAGE = 6;
protected $p = null;
public function collectData() {
$url = sprintf(
'%sv1/group-posts/group/%s/type/%s?',
self::URI,
$this->getGroup(),
$this->getType()
);
$cursor = 'c=10';
$posts = array();
for ($i = 0; $i < $this->getPages(); ++$i) {
$content = getContents($url . $cursor);
$json = json_decode($content, true);
$posts = array_merge($posts, $json['data']['posts']);
$cursor = $json['data']['nextCursor'];
}
foreach ($posts as $post) {
$item['uri'] = $post['url'];
$item['title'] = $post['title'];
$item['content'] = self::getContent($post);
$item['categories'] = self::getCategories($post);
$item['timestamp'] = self::getTimestamp($post);
$this->items[] = $item;
}
}
public function getName() {
if ($this->getInput('d')) {
$name = sprintf('%s - %s', '9GAG', $this->getParameterKey('d'));
} elseif ($this->getInput('g')) {
$name = sprintf('%s - %s', '9GAG', $this->getParameterKey('g'));
if ($this->getInput('t')) {
$name = sprintf('%s [%s]', $name, $this->getParameterKey('t'));
}
}
if (!empty($name)) {
return $name;
}
return self::NAME;
}
public function getURI() {
$uri = $this->getInput('g');
if ($uri === 'default') {
$uri = $this->getInput('t');
}
return self::URI . $uri;
}
protected function getGroup() {
if ($this->getInput('d')) {
return 'default';
}
return $this->getInput('g');
}
protected function getType() {
if ($this->getInput('d')) {
return $this->getInput('d');
}
return $this->getInput('t');
}
protected function getPages() {
if ($this->p === null) {
$value = (int) $this->getInput('p');
$value = ($value < self::MIN_NBR_PAGE) ? self::MIN_NBR_PAGE : $value;
$value = ($value > self::MAX_NBR_PAGE) ? self::MAX_NBR_PAGE : $value;
$this->p = $value;
}
return $this->p;
}
protected function getParameterKey($input = '') {
$params = $this->getParameters();
$tab = 'Sections';
if ($input === 'd') {
$tab = 'Popular';
}
if (!isset($params[$tab][$input])) {
return '';
}
return array_search(
$this->getInput($input),
$params[$tab][$input]['values']
);
}
protected static function getContent($post) {
if ($post['type'] === 'Animated') {
$content = self::getAnimated($post);
} elseif ($post['type'] === 'Article') {
$content = self::getArticle($post);
} else {
$content = self::getPhoto($post);
}
return $content;
}
protected static function getPhoto($post) {
$image = $post['images']['image460'];
$photo = '<picture>';
$photo .= sprintf(
'<source srcset="%s" type="image/webp">',
$image['webpUrl']
);
$photo .= sprintf(
'<img src="%s" alt="%s" %s>',
$image['url'],
$post['title'],
'width="500"'
);
$photo .= '</picture>';
return $photo;
}
protected static function getAnimated($post) {
$poster = $post['images']['image460']['url'];
$sources = $post['images'];
$video = sprintf(
'<video poster="%s" %s>',
$poster,
'preload="auto" loop controls style="min-height: 300px" width="500"'
);
$video .= sprintf(
'<source src="%s" type="video/webm">',
$sources['image460sv']['vp9Url']
);
$video .= sprintf(
'<source src="%s" type="video/mp4">',
$sources['image460sv']['h265Url']
);
$video .= sprintf(
'<source src="%s" type="video/mp4">',
$sources['image460svwm']['url']
);
$video .= '</video>';
return $video;
}
protected static function getArticle($post) {
$blocks = $post['article']['blocks'];
$medias = $post['article']['medias'];
$contents = array();
foreach ($blocks as $block) {
if ('Media' === $block['type']) {
$mediaId = $block['mediaId'];
$contents[] = self::getContent($medias[$mediaId]);
} elseif ('RichText' === $block['type']) {
$contents[] = self::getRichText($block['content']);
}
}
$content = join('</div><div>', $contents);
$content = sprintf(
'<%1$s>%2$s</%1$s>',
'div',
$content
);
return $content;
}
protected static function getRichText($text = '') {
$text = trim($text);
if (preg_match('/^>\s(?<text>.*)/', $text, $matches)) {
$text = sprintf(
'<%1$s>%2$s</%1$s>',
'blockquote',
$matches['text']
);
} else {
$text = sprintf(
'<%1$s>%2$s</%1$s>',
'p',
$text
);
}
return $text;
}
protected static function getCategories($post) {
$params = self::PARAMETERS;
$sections = $params['Sections']['g']['values'];
if(isset($post['sections'])) {
$postSections = $post['sections'];
} elseif (isset($post['postSection'])) {
$postSections = array($post['postSection']);
} else {
$postSections = array();
}
foreach ($postSections as $key => $section) {
$postSections[$key] = array_search($section, $sections);
}
return $postSections;
}
protected static function getTimestamp($post) {
$url = $post['images']['image460']['url'];
$headers = get_headers($url, true);
$date = $headers['Date'];
$time = strtotime($date);
return $time;
}
}

View File

@@ -26,6 +26,10 @@ class NotAlwaysBridge extends BridgeAbstract {
)
));
public function getIcon() {
return self::URI . 'favicon_nar.png';
}
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request NotAlways.');

View File

@@ -0,0 +1,131 @@
<?php
class NyaaTorrentsBridge extends BridgeAbstract {
const MAINTAINER = 'ORelio';
const NAME = 'NyaaTorrents';
const URI = 'https://nyaa.si/';
const DESCRIPTION = 'Returns the newest torrents, with optional search criteria.';
const PARAMETERS = array(
array(
'f' => array(
'name' => 'Filter',
'type' => 'list',
'values' => array(
'No filter' => '0',
'No remakes' => '1',
'Trusted only' => '2'
)
),
'c' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All categories' => '0_0',
'Anime' => '1_0',
'Anime - AMV' => '1_1',
'Anime - English' => '1_2',
'Anime - Non-English' => '1_3',
'Anime - Raw' => '1_4',
'Audio' => '2_0',
'Audio - Lossless' => '2_1',
'Audio - Lossy' => '2_2',
'Literature' => '3_0',
'Literature - English' => '3_1',
'Literature - Non-English' => '3_2',
'Literature - Raw' => '3_3',
'Live Action' => '4_0',
'Live Action - English' => '4_1',
'Live Action - Idol/PV' => '4_2',
'Live Action - Non-English' => '4_3',
'Live Action - Raw' => '4_4',
'Pictures' => '5_0',
'Pictures - Graphics' => '5_1',
'Pictures - Photos' => '5_2',
'Software' => '6_0',
'Software - Apps' => '6_1',
'Software - Games' => '6_2',
)
),
'q' => array(
'name' => 'Keyword',
'description' => 'Keyword(s)',
'type' => 'text'
)
)
);
public function getIcon() {
return self::URI . 'static/favicon.png';
}
public function collectData() {
// Build Search URL from user-provided parameters
$search_url = self::URI . '?s=id&o=desc&'
. http_build_query(array(
'f' => $this->getInput('f'),
'c' => $this->getInput('c'),
'q' => $this->getInput('q')
));
// Retrieve torrent listing from search results, which does not contain torrent description
$html = getSimpleHTMLDOM($search_url)
or returnServerError('Could not request Nyaa: ' . $search_url);
$links = $html->find('a');
$results = array();
foreach ($links as $link)
if (strpos($link->href, '/view/') === 0 && !in_array($link->href, $results))
$results[] = $link->href;
if (empty($results) && empty($this->getInput('q')))
returnServerError('No results from Nyaa: ' . $url, 500);
//Process each item individually
foreach ($results as $element) {
//Limit total amount of requests
if(count($this->items) >= 20) {
break;
}
$torrent_id = str_replace('/view/', '', $element);
//Ignore entries without valid torrent ID
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
//Retrieve data for this torrent ID
$item_uri = self::URI . 'view/' . $torrent_id;
//Retrieve full description from torrent page
if ($item_html = getSimpleHTMLDOMCached($item_uri)) {
//Retrieve data from page contents
$item_title = str_replace(' :: Nyaa', '', $item_html->find('title', 0)->plaintext);
$item_desc = str_get_html(markdownToHtml($item_html->find('#torrent-description', 0)->innertext));
$item_author = extractFromDelimiters($item_html->outertext, 'href="/user/', '"');
$item_date = intval(extractFromDelimiters($item_html->outertext, 'data-timestamp="', '"'));
//Retrieve image for thumbnail or generic logo fallback
$item_image = $this->getURI() . 'static/img/avatar/default.png';
foreach ($item_desc->find('img') as $img) {
if (strpos($img->src, 'prez') === false) {
$item_image = $img->src;
break;
}
}
//Build and add final item
$item = array();
$item['uri'] = $item_uri;
$item['title'] = $item_title;
$item['author'] = $item_author;
$item['timestamp'] = $item_date;
$item['enclosures'] = array($item_image);
$item['content'] = $item_desc;
$this->items[] = $item;
}
}
$element = null;
}
$results = null;
}
}

View File

@@ -0,0 +1,131 @@
<?php
class OnVaSortirBridge extends FeedExpander {
const MAINTAINER = 'AntoineTurmel';
const NAME = 'OnVaSortir';
const URI = 'https://www.onvasortir.com';
const DESCRIPTION = 'Returns the newest events from OnVaSortir (full text)';
const PARAMETERS = array(
array(
'city' => array(
'name' => 'City',
'type' => 'list',
'required' => true,
'values' => array(
'Agen' => 'Agen',
'Ajaccio' => 'Ajaccio',
'Albi' => 'Albi',
'Amiens' => 'Amiens',
'Angers' => 'Angers',
'Angoulême' => 'Angouleme',
'Annecy' => 'annecy',
'Aurillac' => 'aurillac',
'Auxerre' => 'auxerre',
'Avignon' => 'avignon',
'Béziers' => 'Beziers',
'Bastia' => 'Bastia',
'Beauvais' => 'Beauvais',
'Belfort' => 'Belfort',
'Bergerac' => 'bergerac',
'Besançon' => 'Besancon',
'Biarritz' => 'Biarritz',
'Blois' => 'Blois',
'Bordeaux' => 'bordeaux',
'Bourg-en-Bresse' => 'bourg-en-bresse',
'Bourges' => 'Bourges',
'Brest' => 'Brest',
'Brive' => 'brive-la-gaillarde',
'Bruxelles' => 'bruxelles',
'Caen' => 'Caen',
'Calais' => 'Calais',
'Carcassonne' => 'Carcassonne',
'Châteauroux' => 'Chateauroux',
'Chalon-sur-saone' => 'chalon-sur-saone',
'Chambéry' => 'chambery',
'Chantilly' => 'chantilly',
'Charleroi' => 'charleroi',
'Charleville-Mézières' => 'Charleville-Mezieres',
'Chartres' => 'Chartres',
'Cherbourg' => 'Cherbourg',
'Cholet' => 'cholet',
'Clermont-Ferrand' => 'Clermont-Ferrand',
'Compiègne' => 'compiegne',
'Dieppe' => 'dieppe',
'Dijon' => 'Dijon',
'Dunkerque' => 'Dunkerque',
'Evreux' => 'evreux',
'Fréjus' => 'frejus',
'Gap' => 'gap',
'Genève' => 'geneve',
'Grenoble' => 'Grenoble',
'La Roche sur Yon' => 'La-Roche-sur-Yon',
'La Rochelle' => 'La-Rochelle',
'Lausanne' => 'lausanne',
'Laval' => 'Laval',
'Le Havre' => 'le-havre',
'Le Mans' => 'le-mans',
'Liège' => 'liege',
'Lille' => 'lille',
'Limoges' => 'Limoges',
'Lorient' => 'Lorient',
'Luxembourg' => 'Luxembourg',
'Lyon' => 'lyon',
'Marseille' => 'marseille',
'Metz' => 'Metz',
'Mons' => 'Mons',
'Mont de Marsan' => 'mont-de-marsan',
'Montauban' => 'Montauban',
'Montluçon' => 'montlucon',
'Montpellier' => 'montpellier',
'Mulhouse' => 'Mulhouse',
'Nîmes' => 'nimes',
'Namur' => 'Namur',
'Nancy' => 'Nancy',
'Nantes' => 'nantes',
'Nevers' => 'nevers',
'Nice' => 'nice',
'Niort' => 'niort',
'Orléans' => 'orleans',
'Périgueux' => 'perigueux',
'Paris' => 'paris',
'Pau' => 'Pau',
'Perpignan' => 'Perpignan',
'Poitiers' => 'Poitiers',
'Quimper' => 'Quimper',
'Reims' => 'Reims',
'Rennes' => 'Rennes',
'Roanne' => 'roanne',
'Rodez' => 'rodez',
'Rouen' => 'Rouen',
'Saint-Brieuc' => 'Saint-Brieuc',
'Saint-Etienne' => 'saint-etienne',
'Saint-Malo' => 'saint-malo',
'Saint-Nazaire' => 'saint-nazaire',
'Saint-Quentin' => 'saint-quentin',
'Saintes' => 'saintes',
'Strasbourg' => 'Strasbourg',
'Tarbes' => 'Tarbes',
'Toulon' => 'Toulon',
'Toulouse' => 'Toulouse',
'Tours' => 'Tours',
'Troyes' => 'troyes',
'Valence' => 'valence',
'Vannes' => 'vannes',
'Zurich' => 'zurich',
)
)
)
);
protected function parseItem($item){
$item = parent::parseItem($item);
$html = getSimpleHTMLDOMCached($item['uri']);
$text = $html->find('div.corpsMax', 0)->innertext;
$item['content'] = utf8_encode($text);
return $item;
}
public function collectData(){
$this->collectExpandableDatas('https://' .
$this->getInput('city') . '.onvasortir.com/rss.php');
}
}

View File

@@ -0,0 +1,954 @@
<?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
)
));
const LIMIT_ITEMS = 7;
const DAY_SECS = 86400;
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 = date('Ymd', $time);
$quote = $this->getQuote($seed);
$item['title'] = strftime('%A, %x', $time);
$item['content'] = htmlentities($quote, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = $time;
$item['uri'] = 'urn:sha1:' . 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

@@ -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';
}
}
}

100
bridges/PikabuBridge.php Normal file
View File

@@ -0,0 +1,100 @@
<?php
class PikabuBridge extends BridgeAbstract {
const NAME = 'Пикабу';
const URI = 'https://pikabu.ru';
const DESCRIPTION = 'Выводит посты по тегу';
const MAINTAINER = 'em92';
const PARAMETERS = array(
'По тегу' => array(
'tag' => array(
'name' => 'Тег',
'exampleValue' => 'it',
'required' => true
),
'filter' => array(
'name' => 'Фильтр',
'type' => 'list',
'values' => array(
'Горячее' => 'hot',
'Свежее' => 'new',
),
'defaultValue' => 'hot'
)
)
);
public function getURI() {
if ($this->getInput('tag')) {
return self::URI . '/tag/' . rawurlencode($this->getInput('tag')) . '/' . rawurlencode($this->getInput('filter'));
} else {
return parent::getURI();
}
}
public function getIcon() {
return 'https://cs.pikabu.ru/assets/favicon.ico';
}
public function getName() {
if (is_string($this->getInput('tag'))) {
return $this->getInput('tag') . ' - ' . parent::getName();
} else {
return parent::getName();
}
}
public function collectData(){
$link = $this->getURI();
$text_html = getContents($link) or returnServerError('Could not fetch ' . $link);
$text_html = iconv('windows-1251', 'utf-8', $text_html);
$html = str_get_html($text_html);
foreach($html->find('article.story') as $post) {
$time = $post->find('time.story__datetime', 0);
if (is_null($time)) continue;
$el_to_remove_selectors = array(
'.story__read-more',
'svg.story-image__stretch',
);
foreach($el_to_remove_selectors as $el_to_remove_selector) {
foreach($post->find($el_to_remove_selector) as $el) {
$el->outertext = '';
}
}
foreach($post->find('img') as $img) {
$src = $img->getAttribute('src');
if (!$src) {
$src = $img->getAttribute('data-src');
if (!$src) {
continue;
}
}
$img->outertext = '<img src="' . $src . '">';
}
$categories = array();
foreach($post->find('.tags__tag') as $tag) {
if ($tag->getAttribute('data-tag')) {
$categories[] = $tag->innertext;
}
}
$title = $post->find('.story__title-link', 0);
$item = array();
$item['categories'] = $categories;
$item['author'] = $post->find('.user__nick', 0)->innertext;
$item['title'] = $title->plaintext;
$item['content'] = strip_tags(backgroundToImg($post->find('.story__content-inner', 0)->innertext), '<br><p><img>');
$item['uri'] = $title->href;
$item['timestamp'] = strtotime($time->getAttribute('datetime'));
$this->items[] = $item;
}
}
}

View File

@@ -25,6 +25,10 @@ class PinterestBridge extends FeedExpander {
)
);
public function getIcon() {
return 'https://s.pinimg.com/webapp/style/images/favicon-9f8f9adf.png';
}
public function collectData(){
switch($this->queriedContext) {
case 'By username and board':

View File

@@ -15,10 +15,9 @@ class PixivBridge extends BridgeAbstract {
),
));
public function collectData(){
$html = getContents(static::URI.'search.php?word=' . urlencode($this->getInput('tag')))
$html = getContents(static::URI . 'search.php?word=' . urlencode($this->getInput('tag')))
or returnClientError('Unable to query pixiv.net');
$regex = '/<input type="hidden"id="js-mount-point-search-result-list"data-items="([^"]*)/';
$timeRegex = '/img\/([0-9]{4})\/([0-9]{2})\/([0-9]{2})\/([0-9]{2})\/([0-9]{2})\/([0-9]{2})\//';
@@ -40,7 +39,8 @@ class PixivBridge extends BridgeAbstract {
preg_match_all($timeRegex, $result['url'], $dt, PREG_SET_ORDER, 0);
$elementDate = DateTime::createFromFormat('YmdHis',
$dt[0][1] . $dt[0][2] . $dt[0][3] . $dt[0][4] . $dt[0][5] . $dt[0][6]);
$dt[0][1] . $dt[0][2] . $dt[0][3] . $dt[0][4] . $dt[0][5] . $dt[0][6],
new DateTimeZone('Asia/Tokyo'));
$item['timestamp'] = $elementDate->getTimestamp();
$item['content'] = "<img src='" . $this->cacheImage($result['url'], $item['id']) . "' />";
@@ -48,11 +48,11 @@ class PixivBridge extends BridgeAbstract {
}
}
public function cacheImage($url, $illustId) {
private function cacheImage($url, $illustId) {
$url = str_replace('_master1200', '', $url);
$url = str_replace('c/240x240/img-master/', 'img-original/', $url);
$path = CACHE_DIR . '/pixiv_img';
$path = PATH_CACHE . 'pixiv_img/';
if(!is_dir($path))
mkdir($path, 0755, true);
@@ -69,5 +69,4 @@ class PixivBridge extends BridgeAbstract {
return 'cache/pixiv_img/' . $illustId . '.jpeg';
}
}

View File

@@ -58,7 +58,7 @@ class RTBFBridge extends BridgeAbstract {
public function getName(){
if(!is_null($this->getInput('c'))) {
return $this->getInput('c') .' - RTBF Bridge';
return $this->getInput('c') . ' - RTBF Bridge';
}
return parent::getName();

View File

@@ -5,6 +5,10 @@ class RadioMelodieBridge extends BridgeAbstract {
const DESCRIPTION = 'Retourne les actualités publiées par Radio Melodie';
const MAINTAINER = 'sysadminstory';
public function getIcon() {
return self::URI . 'img/favicon.png';
}
public function collectData(){
$html = getSimpleHTMLDOM(self::URI . 'actu')
or returnServerError('Could not request Radio Melodie.');
@@ -23,7 +27,7 @@ class RadioMelodieBridge extends BridgeAbstract {
$item['enclosures'] = array($pictureURL);
$item['uri'] = self::URI . $element->parent()->href;
$item['title'] = $element->find('h3', 0)->plaintext;
$item['content'] = $element->find('p', 0)->plaintext . '<br/><img src="'.$pictureURL.'"/>';
$item['content'] = $element->find('p', 0)->plaintext . '<br/><img src="' . $pictureURL . '"/>';
$this->items[] = $item;
}
}

View File

@@ -7,10 +7,14 @@ class RainbowSixSiegeBridge extends BridgeAbstract {
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Latest articles from the Rainbow Six Siege blog';
public function getIcon() {
return 'https://ubistatic19-a.akamaihd.net/resource/en-us/game/rainbow6/siege-v3/r6s-favicon_316592.ico';
}
public function collectData(){
$dlUrl = 'https://prod-tridionservice.ubisoft.com/live/v1/News/Latest?templateId=tcm%3A152-7677';
$dlUrl .= '8-32&pageIndex=0&pageSize=10&language=en-US&detailPageId=tcm%3A152-194572-64';
$dlUrl .= '&keywordList=175426&siteId=undefined&useSeoFriendlyUrl=true';
$dlUrl .= '8-32&pageIndex=0&pageSize=10&language=en-US&detailPageId=tcm%3A150-194572-64';
$dlUrl .= '&keywordList=233416%2C316144%2C233418%2C233417&siteId=undefined&useSeoFriendlyUrl=true';
$jsonString = getContents($dlUrl) or returnServerError('Error while downloading the website content');
$json = json_decode($jsonString, true);

View File

@@ -9,16 +9,6 @@ class Releases3DSBridge extends BridgeAbstract {
public function collectData(){
function extractFromDelimiters($string, $start, $end){
if(strpos($string, $start) !== false) {
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
return $section_retrieved;
}
return false;
}
function typeToString($type){
switch($type) {
case 1: return '3DS Game';
@@ -76,8 +66,8 @@ class Releases3DSBridge extends BridgeAbstract {
$ignDate = time();
$ignCoverArt = '';
$ignSearchUrl = 'http://www.ign.com/search?q=' . urlencode($name);
if($ignResult = getSimpleHTMLDOM($ignSearchUrl)) {
$ignSearchUrl = 'https://www.ign.com/search?q=' . urlencode($name);
if($ignResult = getSimpleHTMLDOMCached($ignSearchUrl)) {
$ignCoverArt = $ignResult->find('div.search-item-media', 0)->find('img', 0)->src;
$ignDesc = $ignResult->find('div.search-item-description', 0)->plaintext;
$ignLink = $ignResult->find('div.search-item-sub-title', 0)->find('a', 1)->href;
@@ -127,6 +117,7 @@ class Releases3DSBridge extends BridgeAbstract {
$item['title'] = $name;
$item['author'] = $publisher;
$item['timestamp'] = $ignDate;
$item['enclosures'] = array($ignCoverArt);
$item['uri'] = empty($ignLink) ? $searchLinkDuckDuckGo : $ignLink;
$item['content'] = $ignDescription . $releaseDescription . $releaseSearchLinks;
$this->items[] = $item;

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