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

Compare commits

...

671 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
logmanoriginal
de7622ebbf version: Bump to 2018-08-07 2018-08-07 18:37:38 +02:00
logmanoriginal
09c9d015b4 [ForGifsBridge] Add new bridge 2018-08-04 23:42:58 +02:00
logmanoriginal
3a496e3b18 [FilterBridge] Add option to build title from content
Adds a new option '&title_from_content=on' to build the title for feed
items from the feeds content. The title is generated from the first
whitespace after 50 characters of the content or the entire content if
the total size is lower than 50 characters.

References #587
2018-08-04 20:46:59 +02:00
Eugene Molotov
df58f5bbdb [core] Add urljoin (#756)
Adds php-urljoin from https://github.com/fluffy-critter/php-urljoin to replace the custom implementation of 'defaultLinkTo'
2018-08-02 06:31:56 +02:00
logmanoriginal
9d0452d11b [.travis] Use composer for HHVM
This fixes the HHVM build failing because pear doesn't exist in HHVM.
2018-08-01 19:37:10 +02:00
sublimz
f92ac49947 [LeBonCoinBridge] Add cities support (#751) 2018-08-01 17:25:18 +02:00
Benasse
a574fa15ac [YGGTorrentBridge] Order search result by publish date (#762) 2018-07-31 21:46:10 +02:00
Nemo
8f9a385b4d [AmazonPriceTrackerBridge] Improve Amazon scraper logic (#761)
- Now works on all websites, and even with products
  with multiple prices
- Closes #750
2018-07-31 21:44:37 +02:00
logmanoriginal
53bdfa3bf0 [GooglePlusPostBridge] Skip posts without message 2018-07-31 19:15:09 +02:00
logmanoriginal
53278b2eed [GooglePlusPostBridge] Add option to include image in content
References #600
2018-07-31 19:09:12 +02:00
logmanoriginal
5f3c55b808 [GooglePlusPostBridge] General cleanup 2018-07-31 18:55:35 +02:00
logmanoriginal
fb79a67370 [GooglePlusPostBridge] Normalize static::URI usage
This commit fixes a few things related to static::URI

1) Remove trailing slash from the URI to simplify using 'defaultLinkTo'
2) Use static::URI instead of self::URI for consistency
3) Remove custom implementation of 'defaultLinkTo'
2018-07-31 18:29:14 +02:00
logmanoriginal
3c4e12ceba [GooglePlusPostBridge] Add images to enclosures
Images are collected for each post and added to enclosures. Images or
animtions from lh3.googleusercontent.com are specifically handled in
order to return the animated version of the gif and the original sized
image (this is normally taken care of by JS in the browser).
2018-07-31 18:18:22 +02:00
logmanoriginal
0d1923c52f [GitHubGistBridge] Add new bridge
Adds a new bridge for https://gist.github.com

The bridge generates feeds for comments on a particular gist based on
the gist ID or full URI. For better readability the general behavior
of code sections is manually restored with the original CSS styles
from GitHub.
2018-07-29 16:31:47 +02:00
logmanoriginal
ce896b4247 [SkimfeedBridge] Add new bridge
New bridge for Skimfeed: https://skimfeed.com

Generates feeds for all features of Skimfeed:

- News (the ones displayed on the front page)
- Hot topics ("What's Hot" section on the front page)
- Tech news (preconfigured feeds in the menu bar)
- Custom feeds (using the configuration system of Skimfeed), see
https://skimfeed.com/custom.php

The number of items returned by the bridge can be limited for all
categories ('&limit=...'). This parameter is optional, all categories
are unlimited by default!

Authors are added with HTML anchors in order to allow quick navigation
to source channels.

The bridge ships with developer tools to auto-generate lists in the
future (especially useful for 'Tech news'!)

References #748
2018-07-27 23:18:32 +02:00
sysadminstory
a4b2d88dbe [DealabsBridge] Follow website change (#758) 2018-07-25 20:02:31 +02:00
logmanoriginal
65ec04ea98 [contents] Remove superfluous debug log from getContents
References #757
2018-07-25 19:56:46 +02:00
logmanoriginal
afb4de318b [FlickrBridge] Fix missing scheme for image URLs
References #754
2018-07-23 20:14:46 +02:00
Eugene Molotov
43bb17f995 [VkBridge] Converting hashtags to categories (#755)
* [VkBridge] Converting hashtags to categories
2018-07-22 16:43:00 +02:00
logmanoriginal
bae7a5879f [FlickrBridge] Fixed broken bridge
Following changes in the JSON data and selecting images for the
content (320x240 or bigger) and enclosure (largest version). All of
the data is now extracted from the JSON data instead of parsing the
DOM.

References #754
2018-07-22 14:06:04 +02:00
LogMANOriginal
bd760cbcee [README] Add docker build status 2018-07-21 21:59:48 +02:00
LogMANOriginal
cd20b4476f [README] Add label for latest release 2018-07-21 21:54:46 +02:00
LogMANOriginal
d83f2f285b Separate index and bridge card generating code into a separate classes (#734)
[html] Generate index and bridge cards using separate clases

Move HTML generating code from 'index.php' to 'Index.php', separating components into static functions.

Move HTML generation code for bridge cards from 'html.php' to 'BridgeCard.php', separating components into static functions.
2018-07-21 18:15:07 +02:00
logmanoriginal
15e6d77569 [FierPandaBridge] Fix bridge
This bridge now returns all articles from the front page, following
layout changes in the past.

References #679
2018-07-21 18:07:03 +02:00
logmanoriginal
f97d2ef254 [Torrent9Bridge] Remove bridge
The site moved from www.torrent9.pe to www.t9.pe and is now protected
by Cloudflare challenges, making it inaccessible to RSS-Bridge.
2018-07-21 17:45:22 +02:00
logmanoriginal
91ae2a23d7 [CpasbienBridge] Remove bridge
Removing this bridge for two reasons:

1) The service moved from www.cpasbien.cm to www.torrents9.blue,
changing the layout in the process (incompatible).

2) The new site is permanently protected by Cloudflare IUAM, making
it inaccessible by RSS-Bridge.

While it would certainly be possible to rewrite the bridge to work
with the new layout, the site is still inaccessible.

References #605
2018-07-21 17:43:29 +02:00
logmanoriginal
066ef1d7db [contents] Add Cloudflare challenge detection
Adds detection for servers responding with Cloudflare challenges,
throwing a server error if detected:

"The server responded with a Cloudflare challenge, which is not
supported by RSS-Bridge! If this error persists longer than a week,
please consider opening an issue on GitHub!"

This is supposed to support maintainers to identify broken bridges
for sites with Cloudflare enabled permanently. It doesn't circumvent
the protection in any form or shape!

The Cloudflare challenge is detected by analyzing the last response
header received from the server. If the HTTP Code is not 200 (OK)
and the server name contains 'cloudflare' ('Server: cloudflare'),
RSS-Bridge assumes the server responded with a challenge.

The header parsing is based on https://stackoverflow.com/a/18682872
2018-07-21 17:43:29 +02:00
LogMANOriginal
4facbf32e3 [InstructableBridge] Add new bridge (#724)
This commit adds a new bridge for http://www.instructables.com. This bridge
currently supports fetching content by category (all categories available 200+),
using available filters (featured, recent, popular, views, contest winners).
2018-07-21 15:25:13 +02:00
logmanoriginal
6bd76af326 [YoutubeBridge] Add duration limits for all modes
Adds duration limits (minimum duration, maximum duration) for all
modes (user/id/playlist/search). Duration limits are optional, so
existing subscriptions don't break.

The limits are specified by two separate parameters, each of which
is optional:

- `&duration_min=` (minimum duration in minutes, default: -1)
- `&duration_max=` (maximum duration in minutes, default: INF)

If duration limits are specified in either user, id or playlist mode,
the bridge defaults to fetching data from HTML intead of XML feeds,
which requires more bandwidth and takes longer, because each video is
loaded individually!

References #670
2018-07-21 14:33:07 +02:00
logmanoriginal
caa622ffec [search] Support searching by URI
Adds matching for URIs to the search bar, using the format
<scheme>://<host>/<path>

Searching by URI scheme is also supported:

"http://"  (returns all bridges with 'http'  scheme)
"https://" (returns all bridges with 'https' scheme)

The following examples are equivalent and will return both of the
Facebook bridges (FacebookBridge and FB2Bridge):

"https://www.facebook.com/facebook"
"https://www.facebook.com/facebook?..."
"https://www.facebook.com"
"http://www.facebook.com"
"https://facebook.com"
"http://facebook.com"
"facebook.com"
"facebook"

Notice: When the URI scheme is omitted, the search algorithm falls back
to regex matching. Searching for "www.facebook.com" doesn't work, as it
is missing the schema and doesn't match via regex!

Omitting the 'www.', however, does work. This was a design decision for
some bridges specify their URI with and others without 'www.'

A search term can still be specified in the browser URL using parameter
'q' => '?q=searchterm'.

References #743
2018-07-20 22:44:13 +02:00
teromene
c4d489f018 Add URI to ElloBridge elements. 2018-07-19 17:07:54 +02:00
logmanoriginal
6a98293fb3 [Configuration] Bump version to 2018-07-17 2018-07-17 20:44:01 +02:00
logmanoriginal
d79630e3b8 [Configuration] Remove check for allow_url_fopen
This commit follows the changes done in commits

fbf874cb29
ead7b2e8de
2018-07-17 20:39:14 +02:00
teromene
1f2fe25471 Fix LeBonCoinBridge, now uses getContents correctly, 2018-07-17 10:50:30 +02:00
Antoine Cadoret
87fc9e9156 fix LeBonCoin bridge (#747) 2018-07-16 20:13:08 +02:00
Nemo
c7b0c9fd31 Amazon Price Tracker Bridge (#741)
* [amazonprice] Adds AmazonPriceTracker bridge
2018-07-16 14:54:52 +02:00
Teromene
fbf874cb29 Update README.md
Remove allow_url_fopen requirement. This should no longer be necessary. Added requirement for curl.
2018-07-16 12:37:09 +02:00
Eugene Molotov
049ee52fb5 Implemented feed item categories (#746) 2018-07-16 12:32:24 +02:00
TheRadialActive
3f41d0593a Added RSS bridge for zenodo.org (#749)
* added RSS bridge for zenodo.org
2018-07-16 12:02:41 +02:00
sysadminstory
7126f5e838 [DealabsBridge] First version of the generic "Pepper" Bridge (#726)
* [DealabsBridge] First version of the generic "Pepper" Bridge
2018-07-13 00:35:13 +01:00
Nemo
ead7b2e8de [fb2] Switches to getContents (#742) 2018-07-10 02:29:47 +01:00
LogMANOriginal
0d80a19e84 [FacebookBridge] Add context for public Facebook groups (#739)
The previous context is now labeled 'User', while the new context is
labeled 'Group'. The existing code was not changed, instead new group*
functions were implemented to handle groups.

The general principle of capturing groups is the same as done for users
with adjustments to account for different HTML structures.

Captcha responses are currently not supported for groups! There doesn't
seem to be a way to trigger them consistently, which makes it hard to
handle them properly.

Features of the group context:

- The feed title is based on the group name
- The group URI used for capturing is returned for the feed URI
- Author names and timestamps are reproduced from the source
- Post titles are reproduced from the source if they exist, otherwise
the title is build manually from the author name and the content
- Original contents are included with the feed
- All images are attached as enclosures as well

Closes #
2018-07-08 17:16:00 +02:00
logmanoriginal
42c699f474 formats: Fix favicon not found if url contains path 2018-06-30 10:27:05 +02:00
logmanoriginal
2bc8daa101 [JustETFBridge] Add new bridge
Supports latest news and profiling a given ETF in Englisch, German
or Italian language. Cover images are attached as enclosures and not
as part of the content.

News:

Optionally loads the full article for each news item. Some articles
may include scripts to provide interactive graphs. These scripts are
removed as they would be rendered as pure text and a message is shown
instead: "[Content removed! Visit site to see full contents!]"

Profile:

Optionally includes the ETF strategy and description.
2018-06-30 10:27:05 +02:00
logmanoriginal
bca79d3f88 [KununuBridge] Fix broken page layout and sort reviews 2018-06-30 10:27:05 +02:00
logmanoriginal
90dc968fd1 Fix PHPCS error 2018-06-30 10:26:48 +02:00
Teromene
da6b98851c Add recuperation of the current version from git if available (#731)
* Add recuperation of the current version from git if available
* Include version when auto-reporting an error
2018-06-30 10:24:22 +02:00
teromene
71c29d4192 Fix phpcs for master. 2018-06-29 23:15:22 +01:00
LogMANOriginal
193ca87afa [phpcs] enforce single quotes (#732)
* [phpcs] Add rule to enforce single quoted strings
2018-06-29 22:55:33 +01:00
Nemo
5ea79ac1fc Add markdown support to Container Linux Feed (#730) 2018-06-28 20:54:42 +02:00
Teromene
937ea49271 Add basic authentication support (#728)
* Move configuration in its own class in order to reduce the verbosity of index.php
* Add authentication mechanism using HTTP auth
* Add a method to get the config parameters
* Remove the installation checks from the index page
* Log all failed authentication attempts
2018-06-27 19:09:41 +02:00
logmanoriginal
95686b803c [IsoHuntBridge] Remove bridge
isoHunt has discontinued services due to legal reasons and is now
accessible via https://isohunts.to

While it is certainly possible to rewrite the bridge to fetch some
information from the new site, it wouldn't be able to provide as
much functionality as before. This is due to isoHunt having removed
all searching and filtering options, only providing static HTML pages
for general categories (anime, movies, etc...). Those pages, however,
are heavily broken.

Unless someone is interested in monitoring the general categories
the effort of upgrading the bridge to the new site is not worth taking
time for.

Users of isoHunt are asked to make use of their client application,
as they don't provide online services anymore (it's now in the darknet)

Here is the statement from isoHunt:

"Due to hard regulations and security issues for bittorrent users, we
have moved into a more secure and even faster district of the internet!

[...]

Torrent Downloads have a high risk of getting legal problems. That is
why we do not offer torrentfiles any more. [...]"

-- source: https://isohunts.to
2018-06-24 18:33:50 +02:00
logmanoriginal
5087f5f79e [FacebookBridge] Support facebook links as user name
Allows users to paste facebook links as user name. The link must contain
the correct host (www.facebook.com) and a valid path (/user-name/...).
The first part of the path is used for the user name. Errors are returned
in case something went wrong.

References #706
2018-06-24 11:14:08 +02:00
logmanoriginal
4a5f190e0e [FacebookBridge] Add option to skip reviews
Reviews are provided the same way as summary posts and therefore returned
as separate feed item for each review. This commit adds a new option
'&skip_reviews=on' to skip reviews entirely.

References #706
2018-06-24 10:52:22 +02:00
logmanoriginal
01a2746715 [YoutubeBridge] Fix sniff violation
This is a fix for a sniff violation not detected by newer versions
of phpcs (not sure why though, it's detected in version 2.7.1).
2018-06-23 21:28:30 +02:00
Nemo
f4a60c1777 Add dockerfile to create an official docker image (#720) 2018-06-23 16:51:48 +02:00
sysadminstory
1b08bce779 [DealabsBridge] Follow site changes (#721)
- Changed some CSS class to follow the website changes (again)
2018-06-21 13:14:59 +01:00
Nemo
9fa74a36c6 Adds Container Linux releases RSS Feed (#718)
* Adds Container Linux releases RSS Feed
2018-06-19 19:39:08 +01:00
Corentin Garcia
7493e2b5b8 [GrandComicsDatabaseBridge] Add bridge (#717)
closes #709
2018-06-15 21:09:09 +02:00
Corentin Garcia
8e468a9ca7 [SuperSmashBlogBridge] Added bridge (#716) 2018-06-15 21:05:31 +02:00
Joe Digilio
50924b9213 Abort on parse error of config.default.ini.php (#714)
If there is an error parsing the default config file, then abort.
2018-06-15 21:02:06 +02:00
logmanoriginal
4c5013bc82 [index] Bump release version to 2018-06-10 2018-06-10 22:14:58 +02:00
Eugene Molotov
7dc09db9ca [VkBridge] More beatifications and fixes (#712)
* Add one more selector for article_author_selector
* Extend video parsing
* Add poll parsing
2018-06-10 22:09:50 +02:00
hunhejj
d92da8f0f7 Add cUrl error message and code to the debugMessage (#711) 2018-06-10 22:08:45 +02:00
logmanoriginal
064ba456e8 [InstagramBridge] Fix broken compatibility for media_type parameter
The media_type parameter was recently replaced by media_type_u (for
user mode) and media_type_h (for hashtag mode). This was necessary
in order to add the media type 'story' only for the user mode.

"The reason for that is that RSS-Bridge supports multiple parameters
with the same name if and only if they contain the exact same value.
Here, hashtags don't have stories, so it would not be possible to
pass "story" as a parameter. This is a design mistake that I made
when I added support for hashtags."

-- 8770c87389 (r28871502)

However as pointed out this change breaks existing feeds as the
parameter name is no longer compatible to previous implementations.

This commit changes the implementation to provide the old media_type
parameter globally and check for invalid options on each request. If
a user uses the 'story' option in history mode the bridge returns a
client error.

references 8770c87
references #694
fixes #696
fixes #699
fixes #701
2018-05-29 12:52:31 +02:00
LogMANOriginal
8ac8e08abf Add user config (#653)
Uses the parse_ini_file function to load default settings from the default configuration file 'config.default.ini.php'. Optionally loads custom settings from 'config.ini.php' to replace the default
values.
2018-05-29 11:52:17 +02:00
rogerdc
c4f32c31a8 Add ChristianDailyReporterBridge (#697) 2018-05-29 11:28:22 +02:00
Eugene Molotov
4369e077c2 [VkBridge] Fixed image src link generating for photo (#700) 2018-05-29 11:01:54 +02:00
sysadminstory
1045850043 [DealabsBridge] Follow site changes, fix unhandled case (#703)
* [DealabsBridge] Follow site changes, fix unhandled case

- Fixed the case where no discount was shown
- Changed some CSS class to follow the website changes
2018-05-29 10:52:13 +02:00
teromene
2d8f4dc3c5 Fix space in URL resulting in API errors. 2018-05-05 18:10:19 +01:00
teromene
779b638fb4 Added ElloBridge. Closes #683 2018-05-05 18:06:27 +01:00
teromene
3ca59392c2 Fix for crashes when accessing FileCache in case it has been purged/not created yet. 2018-05-05 18:05:48 +01:00
teromene
9b34b68180 Do not use an external service in order to fetch the favicon. 2018-05-05 13:55:38 +01:00
teromene
79ebdc4b39 Warn the user when trying to fetch a non-public facebook page. 2018-05-05 13:49:49 +01:00
teromene
8770c87389 Added support for stories in InstagramBridge. Closes #665
Renamed parameters as stories are only available in user mode.
Use a regex instead of HTML parsing to extract the JSON, as it is way faster.
2018-05-05 13:00:59 +01:00
Eugene Molotov
c1e3352218 [VkBridge] Extended article link parsing (#685)
* [VkBridge] Extended article link parsing
2018-05-05 12:03:54 +02:00
Grégory T
00570ce1b4 [ETTVBridge] New bridge, first push (#680)
* [ETTVBridge] New bridge
2018-04-30 23:18:39 +02:00
teromene
df33dcff4e [YGGTorrentBridge] URL encode the first parts of the requests. 2018-04-26 22:57:18 +01:00
Nicolas Delsaux
e60b5ab193 Mise à jour du bridge pour WorldOfTanks (#527)
* Mise à jour de l'un de mes bridges fétiches
2018-04-22 12:58:07 +02:00
teromene
b0c7a62f74 [index] Bumped version to 2018-04-20 2018-04-20 17:15:25 +02:00
teromene
57b15a089e Added DiscogsBridge. Closes #615 2018-04-20 16:57:09 +02:00
teromene
4b7fbe4188 DansTonChatBridge: test before accessing plaintext 2018-04-19 21:00:18 +02:00
Teromene
2390fb58b3 Merge pull request #673 from GregThib/patch-1
DansTonChatBridge: Update to follow DTC website changes
2018-04-19 20:58:01 +02:00
Teromene
1e8d29f6ec Merge pull request #672 from em92/patch-3
[YoutubeBridge] Removed duration in titles on search mode
2018-04-19 20:56:34 +02:00
Eugene Molotov
644d13686c [YoutubeBridge] Removed duration in titles on search mode 2018-04-19 09:03:29 +05:00
teromene
aa0ff1c9b1 Added YGGTorrentBridge. 2018-04-18 21:57:27 +02:00
teromene
539d9f1f06 Add SupInfoBridge, fixes #668 2018-04-18 12:39:45 +02:00
teromene
5ece801ce7 Fix h* display size in HtmlFormat, and fix images being wider than the page. 2018-04-18 12:29:22 +02:00
GregThib
4dcea6d9c9 Update to follow DTC website changes
Now, entry title is optionnal and may be found in h3 HTML element.
Entry content is mandatory and may be found in div[class="item-content"] HTML element.

Moreover, the title may contain simple quotes (here, encoded) so the bridge have to decode first to apply format library function. In case we don't do that, the format function double encode the quote and something like &amp;#039; could appear.
2018-04-18 12:00:00 +02:00
teromene
d69e2521f1 Removed T411 bridge. Website was closed nearly one year ago. 2018-04-18 11:44:54 +02:00
teromene
7927d73719 Rewrote DemonoidBridge. Fixes #626. 2018-04-17 15:25:02 +02:00
teromene
0620f30ae0 Changed the API key used for SoundCloud bridge. Should fix #599 2018-04-17 14:24:00 +02:00
teromene
795494cfce Added enclosures to InstagramBridge. 2018-04-16 19:34:21 +02:00
teromene
ba8542156c Remove usage of function file_get_contents. 2018-04-16 19:27:20 +02:00
Eugene Molotov
55f112e034 [VkBridge] Rewrited bridge code (#667)
* [VkBridge] Convert special HTML entities to characters in pageName

* [VkBridge] Generate feed item title

* [VkBridge] Remove double backslashes in feed item link

* [VkBridge] Unpin post if pinned

* [VkBridge] Mark reposted messages

* [VkBridge] Correct external link parsing

* [VkBridge] Added article parsing

* [VkBridge] Added video parsing

* [VkBridge] Added photo parsing

* [VkBridge] Added album link parsing

* [VkBridge] Added one more external link selector

* [VkBridge] Using array of link selectors to remove

* [VkBridge] Added document parsing

* [VkBridge] Added sign parsing

* [VkBridge] Fixed incorrect sorting with pinned item

* [VkBridge] More methods to parse documents

* [VkBridge] Save fallback if page name element not found

* [VkBridge] Using post signed as feed item author

* [VkBridge] Fixed document link

* [VkBridge] Coding policy fixes
2018-04-16 10:55:31 +01:00
Mitsukarenai
208fff801d [FDroid] minor fixes for Travis CI 2018-04-15 13:21:48 +02:00
Mitsukarenai
3c9860de43 [FDroid] new bridge 2018-04-15 13:13:10 +02:00
Adam Tygart
a16ec196c5 [NotAlways] Add a bridge for the NotAlways family of sites (#537)
NotAlways right found it necessary to remove their RSS feeds recently. This is a *simple* bridge to grab the ones on the front page. It allows you to filter the articles based on their classification (right, working, romantic, related, learning, friendly, hopeless, unfiltered, or all).
2018-04-15 12:02:37 +01:00
teromene
887fc7b037 Fix GoComics, website completely changed. Fixes #663 2018-04-14 18:15:44 +01:00
teromene
1bd4a40f71 Added GNOME Builder configuration to gitignore. 2018-04-14 18:15:13 +01:00
teromene
494169f959 Added bridge for Pixiv.
This bridge is slow, as caching of images is required (REFERER header required to access the full size images)
2018-04-14 16:19:35 +01:00
logmanoriginal
178177e787 [index] Push version to 2018-04-06 2018-04-06 22:45:33 +02:00
logmanoriginal
1cb83ccea3 [IPBBridge] Use limit for the number of items
The limit was used to specify the number of pages to return from a given
topic which resulted in the number of returned items variing between one
and however many entries are listed on one page.

This commit changes the implementation for the limit to keep loading more
pages until the specified limit is reached. Excessive elements are removed
in order to return the exact amount of items specified by the limit.

This behavior is closer to how other bridges are implemented and makes it
more natural to use without being too confusing. Existing queries must be
updated to account for the new limit.

References #657
2018-04-06 22:25:49 +02:00
sysadminstory
c899399569 [DealabsBridge] Follow the website changes (#660) 2018-04-06 21:25:41 +02:00
LogMANOriginal
0f93370e92 Merge pull request #654 from LogMANOriginal/cURL
Use cURL instead of file_get_contents
2018-04-06 20:49:58 +02:00
logmanoriginal
45c3dcb636 [VkBridge] Simplify header specification 2018-04-06 20:42:19 +02:00
logmanoriginal
ecfc220b10 [KernelBugTrackerBridge] Fix too many parameters requesting HTML DOM 2018-04-06 20:42:19 +02:00
logmanoriginal
4b3efed7ec [YoutubeBridge] Fix too many parameters when using HTML mode 2018-04-06 20:42:19 +02:00
logmanoriginal
bc28c5da8e [contents] Set CURLOPT_HTTPHEADER only if the provided array contains data 2018-04-06 20:42:19 +02:00
logmanoriginal
5bd9c1611d [contents] Limit cURL protocols to HTTP and HTTPS 2018-04-06 20:42:19 +02:00
logmanoriginal
6caca4946b bridges: Fix bridges with custom headers and options
This commit fixes bridges which called getContents, getSimpleHTMLDOM
or getSimpleHTMLDOMCached with custom settings.
2018-04-06 20:42:19 +02:00
logmanoriginal
ee78e7613f [contents] Replace file_get_contents by cURL
cURL is a powerful library specifically designed to connect to many
different types of servers with different types of protocols. For
more detailed information refer to the PHP cURL manual:

- http://php.net/manual/en/book.curl.php

Due to this change some parameters for the getContents function were
necessary (also applies to getSimpleHTMLDOM and getSimpleHTMLDOMCached):

> $use_include_path removed

  This parameter has never been used and doesn't even make sense in
  this context; If set to true file_get_contents would also search
  for files in the include_path (specified in php.ini).

> $context replaced by $header and $opts

  The $context parameter allowed for customization of the request in
  order to change how file_get_contents would acquire the data (i.e.
  using POST instead of GET, sending custom header, etc...)

  cURL also provides facilities to specify custom headers and change
  how it communicates to severs. cURL, however, is much more advanced.

  - $header is an optional parameter (empty by default). It receives
    an array of strings to send in the HTTP request header.

    See 'CURLOPT_HTTPHEADER':

    "An array of HTTP header fields to set, in the format
    array('Content-type: text/plain', 'Content-length: 100')"

    - php.net/manual/en/function.curl-setopt.php

  - $opts is an optional parameter (empty by default). It receives
    an array of options, where each option is a key-value-pair of
    a cURL option (CURLOPT_*) and it's associated parameter. This
    parameter accepts any of the CURLOPT_* settings.

    Example (sending POST instead of GET):

    $opts = array(
      CURLOPT_POST => 1,
      CURLOPT_POSTFIELDS => '&action=none'
    );
    $html = getContents($url, array(), $opts);

    Refer to the cURL setopt manual for more information:
    - php.net/manual/en/function.curl-setopt.php

> $offset and $maxlen removed

  These options were supported by file_get_contents, but there doesn't
  seem to be an equivalent in cURL. Since no caller uses them they are
  safe to remove.

Compressed data / Encoding

  By using cURL instead of file_get_contents RSS-Bridge no longer has
  to handle compressed data manually.

  See 'CURLOPT_ENCODING':

  "[...] Supported encodings are "identity", "deflate", and "gzip".
  If an empty string, "", is set, a header containing all supported
  encoding types is sent."

  - http://php.net/manual/en/function.curl-setopt.php

  Notice: By default all encoding types are accepted (""). This can
  be changed by setting a custom option via $opts.

    Example:

    $opts = array(CURLOPT_ENCODING => 'gzip');
    $html = getContents($url, array(), $opts);

Proxy

The proxy implementation should still work, but there doesn't seem
to be an equivalent for 'request_fulluri = true'. To my understanding
this isn't an issue because cURL knows how to handle proxy communication.
2018-04-06 20:42:19 +02:00
logmanoriginal
2df2623430 [index] Add 'curl' extension check 2018-04-06 20:42:19 +02:00
logmanoriginal
de5f850cdb [index] Fix indentation using tabs 2018-04-06 20:34:44 +02:00
teromene
ac6847045c Catch Errors in order to display a message in more cases. We also catch Exceptions to maintain compat with php 5.
Add check for simplexml extension.
2018-04-04 19:02:40 +01:00
logmanoriginal
df6da837dc [FacebookBridge] Return error if username starts with slash
Requesting a username with a leading slash would cause error 500
because the requested URI would contain two slashes in a row.

For example username "/test" would result in:
https://facebook.com//test

References #628
2018-03-23 21:23:30 +01:00
Eugene Molotov
41b7984a4e [YoutubeBridge] Playlist mode: faster feed generating if item count is less or equal to 15 (#648)
* [YoutubeBridge] Playlist mode: faster feed generating if item count is less or equal to 15
2018-03-19 12:41:52 +00:00
teromene
38c7e0272e Add hashtag support to InstagramBridge.
Fixes  #629
2018-03-19 12:29:24 +00:00
teromene
29c690dbcd Fix InstagramBridge, thanks to @pintassilgo comments.
Fixes #646
2018-03-19 12:17:42 +00:00
LogMANOriginal
8ba817478b Implement customizable cache timeout (#641)
* [BridgeAbstract] Implement customizable cache timeout

The customizable cache timeout is used instead of the default cache
timeout (CACHE_TIMEOUT) if specified by the caller.

* [index] Add new global parameter '_cache_timeout'

The _cache_timeout parameter is an optional parameter that can be
used to specify a custom cache timeout. This option is enabled by
default.

It can be disabled using the named constant 'CUSTOM_CACHE_TIMEOUT'
which supports two states:

> true: Enabled (default)
> false: Disabled

* [BridgeAbstract] Change scope of 'getCacheTimeout' to public

* [html] Add cache timeout parameter to all bridges

The timeout parameter only shows if CUSTOM_CACHE_TIMEOUT has been set
to true. The default value is automatically set to the value specified
in the bridge.

* [index] Disable custom cache timeout by default
2018-03-14 18:06:36 +01:00
Eugene Molotov
cacbe90102 [YoutubeBridge] Sort playlist items by publication date (#643) 2018-03-13 11:24:40 +00:00
Antoine Cadoret
cb91cd5d2f Fix SteamBridge (#637) (#639)
Fixes #639
2018-03-12 09:22:34 +00:00
sysadminstory
52dfa3fe76 [RadioMelodieBridge] Add new bridge (#640) 2018-03-11 15:38:07 +01:00
logmanoriginal
29a1c7ac09 [index.php] Add extension check for 'mbstring'
The mbstring extension is required by all formats in order to convert multi-
byte characters to UTF-8. This commit adds an extension check to throw an
error message if the extension is not enabled.
2018-03-07 19:11:47 +01:00
teromene
6eea51eeeb Fix SteamBridge.
Fixes #636
2018-03-07 10:24:33 +00:00
teromene
2149af0e74 Fix Pinterest bridge, remove the old JSON parsing, and return original sized image.
Fixes #632
2018-03-06 12:01:48 +00:00
teromene
142a647b7a Merge branch 'master' of github.com:RSS-Bridge/rss-bridge 2018-03-06 11:27:37 +00:00
teromene
6e916ddd35 Fix Arte7Bridge.
Fixes #633
2018-03-06 11:26:16 +00:00
Eugene Molotov
159b00145d [VkBridge] Setting feed title (#635)
* [VkBridge] Setting feed title
2018-03-05 09:46:15 +00:00
Mitsukarenai
26ce16baa2 [PlanetLibre] remove bridge (origin now has RSS) 2018-03-03 21:04:40 +01:00
sysadminstory
0622fe142b Dealabs : Added Groupes Feeds and Feed name is set according to parameters (#630)
* [DealabsBride] Added Groupes Feeds
2018-03-01 17:10:34 +00:00
logmanoriginal
4805b52d42 [YoutubeBridge] Fix typo 2018-02-16 22:35:00 +01:00
logmanoriginal
962617086e [YoutubeBridge] Remove superfluous div selectors 2018-02-16 22:31:47 +01:00
logmanoriginal
4f6277b6b5 [YoutubeBridge] Fix parsing author name breaks the bridge
The author name is parsed by searching a string within the entire
HTML document:

$author = $html->innertext;
$author = substr($author, strpos($author, '"author=') + 8);
$author = substr($author, 0, strpos($author, '\u0026'));

This solution will return big portions of the HTML document if
the strpos function returns zero (not found).

This commit replaces the previous implementation by searching for
a specific script tag and making use of the JSON data inside it.

References #580
2018-02-16 22:31:29 +01:00
logmanoriginal
5aaab9eb8c [YoutubeBridge] Skip unavailable videos 2018-02-16 22:11:03 +01:00
sysadminstory
ef402bb5c3 [DealabsBride] Fix for the new site (#595)
* [DealabsBride] Fix for the new site
2018-02-14 11:03:44 +00:00
LogMANOriginal
85ac9001d6 [IPBBridge] Add bridge (#564)
This bridge returns feeds for any URI that is compatible with the
IPB implementation (currently 4.x). Older versions might work, but
there is no guarantee.

Only forum and topic URIs are supported!

The bridge automatically checks if natural feeds are available (by
adding '.xml' to the URI). If so the feed is returned. Otherwise
the bridge will attempt to identify the content type and build a
feed accordingly.

Valid URIs are forums and topics. For forums the first page is
returned, for topics the last one. Elements are ordered such that
the latest entry is returned first (oldest-to-newest)

The optional parameter '&limit=' specifies how many pages should
be loaded (default: 1). Topics are loaded in reverse order.
=> Does not work with forums!

Images are provided as enclosures and scaled to a max-size of
400x400 pixels by default (Except for natural feeds).

The content is filtered before being returned:
- Unnecessary tags are removed (iframes, etc...)
- Styles for blockquotes are restored (grey background)

Closes #507
2018-02-13 21:46:33 +01:00
Mitsukarenai
7939bffcdd fix: TébéoBridge Travis cleanup 2018-02-11 19:08:19 +01:00
Mitsukarenai
bb58aa8e31 New bridge: Tébéo 2018-02-11 16:56:34 +01:00
Ruslan
1d35149191 Update VkBridge (#625) 2018-01-30 16:57:07 +00:00
Tameroski
be03764029 Fixing double quote issue at the end of URL (#623) 2018-01-23 11:27:45 +00:00
Matt DeMoss
a07874d468 Initial commit for Bloomberg bridge with top stories and search (#607)
* initial commit for Bloomberg bridge with top stories and search
2018-01-12 12:08:15 +00:00
Matt DeMoss
90d7ae8776 Fix twitter list filter test #613, fix and change getName() for lists. (#614) 2018-01-12 12:07:40 +00:00
Teromene
93e0562353 Merge pull request #610 from mdemoss/YouTubeTitle-#609
You tube title fix for #609
2018-01-11 12:09:38 +00:00
Teromene
4c5d547d9c Merge pull request #608 from mdemoss/PcGamerBridge
Pc gamer bridge
2018-01-11 12:08:10 +00:00
Teromene
9a3a64010f Merge pull request #620 from RSS-Bridge/teromene-patch-2
Update MixCloudBridge.php
2018-01-11 11:48:29 +00:00
Teromene
e59a6f4c9e Update MixCloudBridge.php
Fix whitespace at start of line
2018-01-11 11:44:51 +00:00
Teromene
1506e68587 Merge pull request #619 from RSS-Bridge/teromene-patch-1
Update .travis.yml
2018-01-11 11:44:37 +00:00
Teromene
671cba4f68 Update .travis.yml
Try to fix build failure
2018-01-11 11:41:25 +00:00
Teromene
374eb8f4bf Merge pull request #617 from adamchainz/patch-1
README - sort lists alphabetically
2018-01-10 14:05:05 +00:00
Adam Johnson
60f7a2b3e4 README - sort lists alphabetically
This makes them easier to scan and check "does rss-bridge support service X I'm interested in?" :)
2018-01-10 11:45:55 +00:00
Teromene
7744172c63 Merge pull request #616 from lalannev/patch-1
Update LegifranceJOBridge.php
2018-01-09 17:19:28 +00:00
lalannev
5a763aee8d Update LegifranceJOBridge.php 2018-01-09 14:57:17 +01:00
Matt DeMoss
c14b2c6905 address phpcs style errors 2017-12-28 20:20:24 -05:00
Matt DeMoss
0871376922 store feed name in new variable, switch getName on queriedContext, remove 'bridge' from name for feeds, fixes #609 2017-12-28 20:20:24 -05:00
Matt DeMoss
c5fe9a6dc0 mark places where a new variable is needed 2017-12-28 20:20:24 -05:00
Matt DeMoss
fbbcd02384 apply phpcbf for automatic style fixes 2017-12-24 16:45:56 -05:00
Matt DeMoss
d34987f9c1 PC Gamer bridge initial commit with most read stories 2017-12-24 16:40:59 -05:00
Teromene
9e0565c655 Merge pull request #604 from TwizzyDizzy/master
Fix double forward-slash in returned post URI leading to 404
2017-12-14 16:43:33 +00:00
Thomas Dalichow
443081c90b Fix double forward-slash in returned post URI leading to 404 2017-12-06 22:17:46 +01:00
Teromene
03fc09e3c6 Merge pull request #602 from TwizzyDizzy/master
Fake user agent as Mixcloud blocks certain User-Agents
2017-12-01 17:29:26 +00:00
Thomas Dalichow
45323c2b2f Fake user agent as Mixcloud blocks certain User-Agents 2017-12-01 17:28:57 +01:00
Teromene
67ee73782c Merge pull request #582 from sysadminstory/master
[DealabsBridge] Add new bridge
2017-10-18 10:53:46 +01:00
sysadminstory
2bb9a29ddc Delete usefull whitespace 2017-10-17 23:37:09 +02:00
sysadminstory
5cbd363597 Coding style fix
Fixed the bridge to follow the project coding style
2017-10-17 23:30:27 +02:00
Teromene
aa6ded0ea4 Merge pull request #593 from b1nj/master
Update saison AllocineFRBridge
2017-10-17 19:02:54 +01:00
sysadminstory
3c61dc2b57 Merge remote-tracking branch 'upstream/master' 2017-10-17 14:53:22 +02:00
B1nj
3e528ddccf Update saisons AllocineFRBridge 2017-10-16 22:24:49 -04:00
teromene
cba65d6d08 [Arte7Bridge] Fix Arte7 bridge, use the API 2017-10-12 18:12:31 +01:00
Teromene
8d418611a2 Merge pull request #589 from mickael-bertrand/patch-2
Updater torrent9 URI
2017-10-12 17:18:04 +01:00
teromene
98b0f0f8ba [Core] Verify the presence of the array keys before accessing them.
Fixes  #588
2017-10-12 17:14:34 +01:00
Teromene
6f66e6d9be Merge pull request #592 from ldidry/fix-gocomics
Update GoComicsBridge
2017-10-11 11:34:37 +01:00
Luc Didry
8b06299bad Update GoComicsBridge 2017-10-11 10:03:29 +02:00
MickaëlBERTRAND
5a99981827 Updater torrent9 URI 2017-10-08 19:21:10 +02:00
Teromene
e30ad3feb4 Add support for running rss-bridge from the CLI 2017-09-25 19:14:02 +02:00
logmanoriginal
77657a9154 [.travis.yml] Refactor script 2017-09-24 18:45:58 +02:00
logmanoriginal
3059b1ea80 [YoutubeBridge] Skip Ads
The search might return unrelated videos (Ads) that are inserted
between regular search results. This adds a check to skip Ads.

Closes #571
2017-09-24 17:25:47 +02:00
LogMANOriginal
4037c34393 [TwitterBridge] Add category for lists (#545)
This adds a new option to generate feeds from Twitter lists using
an optional filter (string comparison).
2017-09-24 16:59:45 +02:00
logmanoriginal
e671a2ad02 [.travis.yml] Fix configuration to work with Ubuntu Trusty
Travis CI upgraded the linux build environment from Ubuntu Precise
to Ubuntu Trusty with Trusty becoming the default build environment
as of August 2017:

  https://docs.travis-ci.com/user/reference/overview/

A bug in the configuration of the Ubuntu Trusty distro causes all
builds except nightly to fail. The PHP include_path is set to

  include_path='.:/home/travis/.phpenv/versions/5.6.31/share/pear'

instead of

  include_path='.:/home/travis/.phpenv/versions/5.6.31/lib/php/pear'

which causes phpcs to fail because it cannot resolve import paths.

This commit adds a hotfix to .travis.yml that circumvents the
issue by overwriting the include_path during initialization. This
hotfix should be removed once a solution is found.

This bug is tracked via
https://github.com/travis-ci/travis-ci/issues/8487
2017-09-23 20:48:28 +02:00
logmanoriginal
1ea091f215 [.travis.yml] Fix Tavis CI build error
Travis CI recently updated the default distribution from Ubuntu
Precise to Ubuntu Trusty, which causes all builds except nightly
to fail.

For unknown reasons phpcs is unable to locate "PHP/CodeSniffer/
autoload.php" causing it to fail with a fatal error

The root cause of the failure is unknown. We explicitly return to
the previous build system (Ubuntu Precise) for builds to work again.

See the migration guide for reference:
https://docs.travis-ci.com/user/precise-to-trusty-migration-guide/

Notice: Ubuntu Precise is retired as of September 2017 and will be
decommissioned in the near future:
https://docs.travis-ci.com/user/reference/overview/
2017-09-23 18:38:47 +02:00
logmanoriginal
87fa4ae3ac [.travis.yml] Fix warning channel pear.php.net has updated its protocol 2017-09-23 18:05:09 +02:00
sysadminstory
d7a1dca004 [DealabsBridge] Conform to coding policy
- If no there are no results, an explicit message is now returned
- Commas are now following the coding policy
- Lines are no longer more than 80 chars when possible
2017-09-19 02:08:22 +02:00
sysadminstory
fe48340327 [DealabsBridge] Add new bridge 2017-09-05 21:03:21 +02:00
logmanoriginal
b4c6aa41a7 [index] Return error if no format is specified when requesting a bridge 2017-08-28 20:45:06 +02:00
metaMMA
1696aee212 [DemonoidBridge] Add new bridge 2017-08-28 20:00:52 +02:00
metaMMA
585379d47a [ThePirateBayBridge] Add instructions
Added additional instructions for: 'username search' and 'category
search' next to instructions for 'keyword search'.

Changed variable name from underscore to camelCase.
2017-08-28 20:00:00 +02:00
logmanoriginal
2595b5d7d8 [index] Bump version 2017-08-19 21:09:48 +02:00
logmanoriginal
f858adc884 [CHANGELOG] Add 2017-08-19 2017-08-19 21:08:44 +02:00
logmanoriginal
44e135ce1e [CHANGELOG] Fix layout 2017-08-19 20:12:17 +02:00
logmanoriginal
9a9ce30b16 [YoutubeBridge] Fix issues loading playlists
Videos that are part of a playlist have the playlist ID encoded in
the URI. When loading the video info the page contents change unex-
pectedly due to the playlist being part of the page.

This removes any trailing parameters from the video ID in order to
ensure only pure videos are loaded at all times.
2017-08-19 18:51:30 +02:00
logmanoriginal
0e2b80d5d7 [YoutubeBridge] Fix error on certain keywords
References #569
2017-08-17 19:26:04 +02:00
logmanoriginal
1b1ab6a66e [validation] Fix error on undefined optional numeric value
Providing no value for an optional numeric parameter results in
error "Parameter *** is invalid!"

This is caused by the validation function ignoring the 'required'
attribute when loading and checking input parameters.

This commit adds checks to determine whether the 'required' attri-
bute is defined and active before returning the error message.

References #570:
2017-08-17 19:02:50 +02:00
mcbyte-it
0284e9d488 [GoComicsBridge] Fix for page structure changes (#568)
GoComics changed comic page structure, so this patch fixes it

Closes #565
2017-08-17 18:35:41 +02:00
logmanoriginal
f91309c7e4 [index] Use constant WHITELIST_FILE all the way 2017-08-12 19:15:16 +02:00
logmanoriginal
cd012e115b [index] Show bridge options when loading with URL fragment
Loading the page with an URL fragement (#bridge-*) should result in
the bridge showing all parameters by default. Unfortunately this is
not possible using PHP, which is why a new JavaScript function is
needed (select.js)

That way, when returning from a bridge ('back to rss-bridge') will
keep the selected bridge active (only works for HTML format).
2017-08-11 19:39:16 +02:00
logmanoriginal
df9e3968dc [index] Add GET parameter 'q' for search queries 2017-08-11 17:43:15 +02:00
logmanoriginal
c237eaa254 [style] Fix All input boxes are center aligned
f757d7d1a5 introduced a bug where all
text input boxes were centered instead of just the search bar.

In order for this to work properly the global styles must be applied
before specific styles for the search bar.
2017-08-10 20:27:27 +02:00
logmanoriginal
f757d7d1a5 [style] Center search cursor and hide placeholder
The search bar doesn't feel right if the placeholder is centered,
while the text cursor is left-aligned. The cursor should appear
instead of the placeholder (at the same position).

Added styles to center the text cursor and hide the placeholder
when selecting the input field.

Tested in:
 - Firefox 54 & 55
 - Chromium 60 (compatible with Chrome 60)
 - Microsoft Edge (partially working!)

--- Microsoft Edge ---

Due to a bug in the Microsoft Edge browser, the text cursor is not
centered as long as the placeholder is defined (which it is always)

More information:
  https://stackoverflow.com/a/33224868

Official bug report:
  https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/4468563/

----------------------
2017-08-10 14:35:09 +02:00
logmanoriginal
4fb1366aaf [FeedExpander] Fix Serialization of 'SimpleXMLElement' is not allowed 2017-08-10 13:35:19 +02:00
logmanoriginal
8166e33e7f [FeedExpander] Remove whitespace from source content
Whitespace at the beginning of feeds causes parsing errors. This is
an example using an ill-formatted RSS feed:

   "XML or text declaration not at start of entity"
-- https://validator.w3.org

This commit automatically removes all proceeding and trailing white-
space from the source content before resume parsing.
2017-08-10 13:20:35 +02:00
Quentin de Longraye
ff3b1c9eb2 [DribbbleBridge] Add dribble bridge listing last dribble popular shots (#558) 2017-08-06 20:29:21 +02:00
logmanoriginal
4924769549 [validation] Remove superfluous if-statement 2017-08-06 13:45:24 +02:00
logmanoriginal
e4fa963bdf [validation] Return null on invalid number 2017-08-06 13:43:23 +02:00
logmanoriginal
54e8bb2228 [VineBridge] Remove bridge
On Oct 27, 2016 the discontinuation of Vine was announced:
https://medium.com/@vine/important-news-about-vine-909c5f4ae7a7

"Today, we are sharing the news that in the coming months we’ll be
discontinuing the mobile app."

https://vine.co/ is still online, but has been put into an archive
indefinitely. As the site does not allow further uploads, this
bridge serves no further purpose.
2017-08-06 13:03:10 +02:00
logmanoriginal
99e7e7876e exception: Use built-in HTTP response codes
PHP >= 5.4 provides a built-in function to generate valid HTTP
error header including the error description: http_response_code()

See: http://php.net/manual/en/function.http-response-code.php
See also: https://stackoverflow.com/a/12018482

This commit removes the '\Http' utility class and replaces all
calls to 'Http::getMessageForCode()' by 'http_response_code()'
2017-08-06 12:55:11 +02:00
logmanoriginal
62c190d841 [Bridge] Remove superfuous variables and statements 2017-08-06 00:04:07 +02:00
logmanoriginal
84d2c02a09 whitelist: Do case-insensitive whitelist matching
Matching whitelisted bridges using a case-insensitive match makes
sense for following reasons:

- Wrong upper/lower case spelling in the whitelist is not easily
discovered. Example: Misspelling 'Youtube' as 'YouTube' will not
show the 'Youtube' bridge (while it is expected to show)

- Two bridges with the same name but different letter casing are
discouraged to prevent confusion and keep the project compatible
with Windows machines
2017-08-06 00:01:32 +02:00
logmanoriginal
fc0ae42450 [GelbooruBridge] Fix bridge not getting tags correctly
Tags are embedded in the 'title' attribute instead of 'alt' as
defined by the ancestor (DanbooruBridge).

The 'title' attribute also contains statistics data ('score:...',
'rating:...') that is now filtered by a custom implementation of
the 'getTags' function (elements that contain a colon are removed)

Closes #560
2017-08-05 22:38:24 +02:00
logmanoriginal
9599f921a5 [DanbooruBridge] Allow descendant classes to override tag collection
Add protected function 'getTags' that receives the current element
and returns a string containing all tags.

References #560
2017-08-05 22:36:14 +02:00
logmanoriginal
e125e9aba1 [LeBonCoinBridge] Fix bridge is marked executable
Closes #561
2017-08-05 22:00:58 +02:00
Pierre Mazière
55a77c734d [LWNprevBridge] Fix everchanging url
Signed-off-by: Pierre Mazière <pierre.maziere@gmx.com>

Closes #563
2017-08-05 15:56:35 +02:00
logmanoriginal
ccd8af09b9 [index] Use single quotes instead of double quotes 2017-08-05 15:46:16 +02:00
logmanoriginal
f2d02a4187 [index] Simplify debug mode detection
This removes superfluous variables and if-statements when checking
whether the debug mode is active or not.
2017-08-05 15:43:48 +02:00
logmanoriginal
f19d34a5a1 [index] Check permissions for cache folder and whitelist file
* The cache folder requires write permissions at all times
* The whitelist file requires write permissions if it does not
exist (can be created manually)
2017-08-05 15:23:30 +02:00
logmanoriginal
f1534c91e2 [index] Use constant instead of variable for the whitelist file path
Like the cache folder the whitelist file is assumed static and thus
should be defined as constant.
2017-08-05 15:23:08 +02:00
logmanoriginal
cbda060b86 [FacebookBridge] Fix &amp; in URLs
All formats except HTML return &amp; instead of & in URLs causing
all links with parameters (...&id=...) to break.

Facebook does not return valid HTML URIs but instead provides them
with all special characters encoded (like using htmlspecialchars).
This seems to be related to the page being build almost entirely of
script blocks.

This commit adds htmlspecialchars_decode() to URI and content to
reverse the encoding.

References #550
2017-08-04 21:12:48 +02:00
logmanoriginal
f7265ca77b [index] Bump version number 2017-08-03 20:51:59 +02:00
logmanoriginal
629a4c4481 [CHANGELOG] Add 2017-08-03 2017-08-03 20:49:59 +02:00
logmanoriginal
950ae2cc05 [CHANGELOG] Change order of appearance 2017-08-03 20:49:39 +02:00
Pierre Mazière
873a91259f [LWNprevBridge] full rewrite
Signed-off-by: Pierre Mazière <pierre.maziere@gmx.com>
[logmanoriginal@users.noreply.github.com: Fix coding style]
2017-08-03 19:39:50 +02:00
logmanoriginal
c7ec50373a Merge branch 'TwitterWithoutPromotedTweets'
Closes #556
2017-08-03 18:26:06 +02:00
logmanoriginal
c986ff9116 [TwitterBridge] Fix coding style 2017-08-03 17:56:39 +02:00
Pierre Mazière
485b465a24 [TwitterBridge] ignore promoted tweets
Signed-off-by: Pierre Mazière <pierre.maziere@gmx.com>
2017-08-03 00:44:21 +02:00
logmanoriginal
a4b9611e66 [phpcs] Add missing rules
- Do not add spaces after opening or before closing parenthesis

  // Wrong
  if( !is_null($var) ) {
    ...
  }

  // Right
  if(!is_null($var)) {
    ...
  }

- Add space after closing parenthesis

  // Wrong
  if(true){
    ...
  }

  // Right
  if(true) {
    ...
  }

- Add body into new line
- Close body in new line

  // Wrong
  if(true) { ... }

  // Right
  if(true) {
    ...
  }

Notice: Spaces after keywords are not detected:

  // Wrong (not detected)
  // -> space after 'if' and missing space after 'else'
  if (true) {
    ...
  } else{
    ...
  }

  // Right
  if(true) {
    ...
  } else {
    ...
  }
2017-07-29 19:55:12 +02:00
LogMANOriginal
38b56bf23a [index] Improve error handling (#555)
Add additional information to error message:

- Name of the bridge
- Possible solutions
- Error description
- Error code
- Error message

* Output type changed from 'text' to 'html'
* Added styles for the error page
* Added a button to remotely open a GitHub issue

Closes #525
2017-07-29 19:16:16 +02:00
logmanoriginal
6e4bc341b7 [FacebookBridge] Replace 'novideo' with 'media_type'
This replaces the 'novideo' parameter with 'media_type' in order
to filter for specific content types. Currently supported:

- 'all': Returns all posts (default)
- 'video': Returns only posts including videos
- 'novideo': Returns only posts that don't include videos

References #553
2017-07-25 16:04:21 +02:00
logmanoriginal
fa2df09b1b [FacebookBridge] Add option to hide posts with facebook videos
This adds a new option 'novideo' that can be set to 'on' or 'off'
in order to skip posts that include facebook videos (does not work
for linked videos like YouTube). This option is 'off' by default.

References #533
2017-07-25 15:41:05 +02:00
logmanoriginal
7dda088b3f [InstagramBridge] Add option to filter for videos and pictures
Adds a new option 'media_type' to select from three choices:

- 'all' (Both): Returns pictures and videos (default choice)
- 'picture': Returns only pictures
- 'video': Returns only videos

References #553
2017-07-25 15:14:37 +02:00
logmanoriginal
f6f3a213ef [DanbooruBridge] Fix broken URI
This fixes broken URIs in the output data caused by duplicate domain
names caused by sites (descendant class Delbooru) providing absolute
URIs instead of relative ones.

References #552
2017-07-25 14:43:29 +02:00
Antoine Cadoret
1faa91ef0f Add SteamBridge (#543) 2017-07-17 15:45:58 +02:00
Corentin Garcia
5caca62677 Update RainbowSixSiegeBridge (#548)
* Fix non-working RainbowSixSiegeBridge

* Updated RainbowSixSiegeBridge to use API to fetch articles

* Fix RainbowSixSiegeBridge coding style

* RainbowSixSiegeBridge fix url coding style error
2017-07-14 22:05:51 +02:00
logmanoriginal
d7ff8b9ac7 [TwitterBridge] Fix title includes anchors in plaintext format
The title attribute includes tags (anchors) instead of raw text.
While this works fine in a browser, using a raw format like plain-
text or json breaks with expected behavior.

This commit changes the order in which functions are applied. By re-
moving anchors AFTER fixing the title, the final result does not
include tags and the title is still fixed.

This bug was introduced by d81b61ccfa

References: #546
2017-07-05 18:42:03 +02:00
logmanoriginal
ab46af9719 [TwitterBridge] Avoid empty content caused by new login policy
Twitter now requires login to access "Tweets & Replies" which
breaks feeds using the default behavior. Using the "Without
replies" option still works.

This commit makes the "Without replies" option default. That way
existing feeds will return contents again. The parameter can still
be checked but its status has no effect anymore.

Notice: The parameter should not be removed as that would cause
any feed using the parameter to stop working because of "Invalid
parameter"

References #544
2017-07-03 19:53:18 +02:00
LogMANOriginal
06babeb644 Merge pull request #541 from Frenzie/filterbridge_real
[FilterBridge] Initial implementation of basic title permit and block
2017-07-03 19:15:28 +02:00
LogMANOriginal
341010b391 Merge pull request #535 from Frenzie/filterbridge
[FeedExpander] Deal with empty item
2017-07-03 19:13:12 +02:00
Frans de Jonge
995d78fa5a [FilterBridge] Initial implementation of basic title permit and block
See the comment https://github.com/RSS-Bridge/rss-bridge/issues/402#issuecomment-305982306

Split off from https://github.com/RSS-Bridge/rss-bridge/pull/535
2017-06-24 15:11:40 +02:00
Frans de Jonge
781e4f1908 [FeedExpander] Deal with empty item 2017-06-24 15:09:15 +02:00
logmanoriginal
ae59b20c0c [TwitterBridge] Fix double slashes in URI
This fixes double slashes in the feed URI (https://twitter.com//...
instead of https://twitter.com/...)

Reported via #538
2017-06-19 00:19:55 +02:00
logmanoriginal
d81b61ccfa [TwitterBridge] Fix missing spaces
This commit improves readability of tweets by adding spaces before
anchors in the text.

- Hide "invisible" tags which were not rendered hidden because of
missing CSS.
- Fix spacing between anchors

Reported via #539
2017-06-19 00:17:46 +02:00
Teromene
9c78362fd7 Warn when accessing a private page. 2017-06-15 11:51:11 +01:00
Teromene
18c6f0126f Fix FB2 bridge 2017-06-15 11:42:59 +01:00
LogMANOriginal
d5f47efcea Merge pull request #533 from Frenzie/patch-2
[RTBFBridge] Update URI
2017-05-28 20:37:11 +02:00
Frans de Jonge
601f61f063 [RTBFBridge] Update URI
A series URL still looks like: https://www.rtbf.be/auvio/emissions/detail?id=3553

But an individual episode has been changed from https://www.rtbf.be/auvio/emissions/detail?id=2217881 to https://www.rtbf.be/auvio/detail?id=2217881
2017-05-28 20:15:59 +02:00
logmanoriginal
8ed4812e00 [FacebookBridge] Add requester languages to HTTP header
If no accepted languages are specified Facebook will guess your
language. This guess can go horribly wrong if your server does not
provide origin information.

This adds a context header with language information when retrieving
page contents. The accepted languages are read from the list of
accepted languages specified by the web browser of the requester.

References #530
2017-05-07 13:27:37 +02:00
Teromene
f38db4d79e Merge pull request #528 from Jocker666z/patch-1
Update URI of t411 bridge
2017-05-04 12:30:18 +01:00
Jocker666z
88d1068406 update uri 2017-05-04 12:01:14 +02:00
logmanoriginal
627038e2fa [YoutubeBridge] Improve URL handling in video descriptions
This improves the translation of regular text to anchors by adding
support for additional characters '?&=-_' to fix common URLs.

Notice: The regex pattern is by no means complete. That means it is
likely to break in the future. More sophistiated solutions however
are insanely complex. See: http://stackoverflow.com/a/190405

References #520
2017-05-02 22:03:44 +02:00
logmanoriginal
5b541e380a [TwitterBridge] Optimize returned image sizes
Twitter provides an easy way to receive various image sizes based
on the same image URI:

https://dev.twitter.com/overview/api/entities-in-twitter-objects

We support different sizes: thumb, small, medium and large. The
media_url defaults to medium but you can retrieve the media in
different sizes by appending a colon + the size key (for example:
https://pbs.twimg.com/media/A7EiDWcCYAAZT1D.jpg:thumb).

-- Twitter Developer Documentation

TwitterBridge now makes use of this feature in order to provide
thumbnail images in the content and original sized images as
enclosures.

References #526
2017-05-02 21:45:26 +02:00
Mitsu
c375ddd6ab Merge pull request #521 from Frenzie/master
WikipediaBridge: fix French Wikipedia
2017-04-28 19:26:54 +02:00
Frans de Jonge
44c3110db0 WikipediaBridge: fix French Wikipedia 2017-04-28 19:15:23 +02:00
logmanoriginal
120e74c1b4 [YoutubeBridge] Improve readability of feed contents
Previously feed contents were rendered as one block of text with
no structure. This brings back the structure of original video
descriptions and makes links in the description work again.

References #520
2017-04-27 21:44:26 +02:00
LogMANOriginal
890ba69116 Merge pull request #518 from rogerdc/master
Adding DiceBridge.php
2017-04-25 21:59:57 +02:00
LogMANOriginal
d6da2ce406 Merge pull request #519 from sysadminstory/master
[AllocineFRBridge] Update Faux Raccord link
2017-04-25 21:48:48 +02:00
Gilles Maurer
0eb5711a68 [AllocineFRBridge] Update Faux Raccord link
This update the link to last season of the Show "Faux raccord"
2017-04-25 01:11:50 +02:00
rogerdc
a4ef42c2e9 Adding DiceBridge.php
Dice.com is a technology-oriented job search site. This bridge allows you to create RSS feeds for the jobs listed there.

Happy job hunting!
2017-04-23 19:19:50 -05:00
logmanoriginal
28331e7cd6 [BridgeAbstract] Return cached infos when using cached items
Re-requesting the same feed in normal mode (not debug mode)
returns the cached version. The name and URI of the feed
however was not returned.

This adds checks to getName() and getURI() in order to return
the cached infos when using the cached version.

Notice: For this to work correctly all bridges must call to
parent::getName() and parent::getURI() respectively if the
queriedContext is not defined. Example:

switch($this->queriedContext){
	case 'My context':
		// do your stuff here!
		break;
	default: parent::getName();
}
2017-04-23 21:06:25 +02:00
logmanoriginal
6eadc6ca6f [TwitterBridge] Show quotes and pictures
This adds new features to show quotes and pictures in feeds.

Quotes will show up on top of a tweet and are separated from
the quoting feed by a horizontal line.

Pictures that are embedded in the tweet will be captured and
attached to the feed using enclosures. By default the picture
will also be shown in the feed itself. This can be disabled
using the option '&noimg=on'

Some codes are now split into separate functions so they can be used
for tweets and quotes alike.
2017-04-22 16:01:00 +02:00
logmanoriginal
638d173b70 [PinterestBridge] Fix checkbox not working
Changed behaviour of the checkbox to use the custom parser when
active. That way if the parameter is missing the default value
applies and the feed returns from the provided RSS

Reported via #498
2017-04-10 14:34:45 +02:00
logmanoriginal
a9535797e6 [ShanaprojectBridge] Don't throw error if timestamp is missing 2017-04-10 13:38:02 +02:00
logmanoriginal
fc9084eb17 [MangareaderBridge] Fix double forward slashes
Double forward slashes caused all external links in the feed to break
2017-04-10 13:20:07 +02:00
logmanoriginal
e221358ead [FacebookBridge] Handle summary posts
Previously summary posts were ignored which resulted in the last
two posts not showing up in the feed (the latest two are shown in
the summary post).

Now summary posts are treated like regular posts, returning them
as part of the regular feed.

References #502, #505
2017-04-10 13:04:41 +02:00
logmanoriginal
2500d0df93 [PinterestBridge] Fix implementation after DOM changes
Due to breaking DOM changes this bridge required re-implementation.
With this fix the brige will make use of the JSON data embedded in
the returned HTML. The content returned for all contexts is similar
with only a few differences due to limitations of the JSON.

Feeds returned for a given username and board will by default make
use of the provided RSS feed instead of using the custom filter.
This bahaviour can be changed by setting the  optional parameter
'&r=off' (on by default)

Notice: The JSON data for userdata and search results is very
different, so two functions were implemented to account for that.

References #498
2017-04-09 23:38:35 +02:00
logmanoriginal
4124c707d4 [SexactuBridge] Fix typo 2017-04-09 21:44:05 +02:00
logmanoriginal
8e84b52152 [SexactuBridge] Fix URI and timestamp
* const DOMAIN is not supported, it must be const URI
* strtotime should be used instead of date_parse in order to
receive a valid integer
* Some small readability enhancement
2017-04-09 21:33:50 +02:00
Nicolas Delsaux
f3b6b264d3 [SexactuBridge] Use most modern version of bridge api and cached pages (#504)
Fixed #503 to use most modern version of bridge api and cached pages
2017-04-09 21:15:01 +02:00
logmanoriginal
360f9da072 [EtsyBridge] Add new bridge
This bridge generates feeds for a given search term, optionally
adds the picture to the content and allows for additional query
extensions (GET parameters) to be passed to the bridge. That
way custom filter can be applied without the need to reproduce
them in this bridge (they got a lot!)

Etsy provides a good set of feeds as described here:
https://www.etsy.com/help/article/100

(so there is no need to include them here)

References #492
2017-03-27 20:18:19 +02:00
logmanoriginal
e3b335b9ff [WikiLeaksBridge] Add new bridge
This bridge will fetch contents from https://wikileaks.org

Available options are:

- Category: Defines a list of categories to select from
- Show teaser: Defines whether to show the teaser or not

Notice: Feeds provided by WikiLeaks do not work, see
https://wikileaks.org/wiki/RSS

Closes #489
2017-03-26 17:58:26 +02:00
logmanoriginal
9acd30a5c5 [GooglePlusPostBridge] Autofix user names
User names can either be an ID (series of numbers), or an actual
name, where the name always starts with a '+'.

This commit adds a check for automatically fixing provided user
names which are missing the '+'.
2017-03-26 16:50:42 +02:00
logmanoriginal
3276d4e3d5 [GooglePlusPostBridge] Fix content loading
- Do not force language via HTTP header
The header enforced the language to be french which caused problems parsing
the exact time due to spellings (strtotime cannot work with 'semaines'). If
further issues are experienced try forcing en-us instead.
=> This should really be done in the RSS-Bridge core

- Fix loading problems due to pinned articles
Pinned articles do not provide a timestamp. Building the timestamp step-by-step
solves parsing errors.

- Use class names instead of CSS paths
CSS paths change based on the article. Pinned articles provide a different
DOM structure which caused parsing errors.

Reported via #499
2017-03-26 16:41:20 +02:00
Corentin Garcia
88586381e7 [GithubSearchBridge] Added github search bridge (#500)
* [GithubSearchBridge] Added github search bridge, only repos search atm
2017-03-25 11:24:00 +00:00
niawag
ebe897f120 Create KATBridge.php (#501)
Create KATBridge.php
2017-03-25 11:23:36 +00:00
Teromene
1a4c3f4418 Add a search bar to simplify looking for a bridge. (#494)
* Add a search bar to simplify looking for a bridge.

* Fix phpcs line length.

* Change the phpcs config.
2017-03-21 20:31:10 +00:00
Corentin Garcia
2ac0469750 Updated 4 bridges to use HTTPS (#497)
* [NextInpactBridge] Use https

* [InstagramBridge] Use https

* [GBAtempBridge] Use https

* [LeBonCoinBridge] Use https
2017-03-21 20:27:12 +00:00
Teromene
c0181d8d41 Merge pull request #496 from corenting/patch-2
[RainbowSixSiegeBridge] Added bridge for Rainbow Six Siege blog
2017-03-21 20:26:36 +00:00
Corentin Garcia
ea3073e27f [RainbowSixSiegeBridge] Added bridge for Rainbow Six Siege blog 2017-03-20 21:32:31 +01:00
LogMANOriginal
20ea75994d Merge pull request #495 from corenting/patch-1
[NasaApodBridge] Use HTTPS instead of HTTP
2017-03-20 19:02:32 +01:00
Corentin Garcia
a84c245fa0 [NasaApodBridge] Use HTTPS instead of HTTP 2017-03-20 14:26:08 +01:00
logmanoriginal
b48a44c979 [UsbekEtRicaBridge] Add new bridge
Adds a new bridge to fetch contents from https://usbeketrica.com/
Feeds are build from cards displayed on the front page

This bridge provides two options:
- limit: Defines how many articles are returned
- fullarticle: Defines whether or not the full article is retured

Requested via #457
2017-03-19 14:32:59 +01:00
logmanoriginal
c6ce453c47 [MixCloudBridge] Fix bridge broken after DOM changed
This commit fixes DOM changes reported via #436
New DOM introduced via
https://blog.mixcloud.com/2017/01/10/take-a-look-at-the-new-and-improved-mixcloud/
2017-03-19 12:04:59 +01:00
logmanoriginal
bd92392921 [GooglePlusPostBridge] Fix bridge implementation
This bridge was broken due to DOM changes. This commit fixes
most of the broken code. Hashtags do no longer work because
they are no longer supported/provided.

The timing might be off as the source only provides a rough
relative value like '1 hour' or '1 year'.

Closes #485
2017-03-18 21:09:06 +01:00
Teromene
59025d96bc Add an indicator to show the HTTP status of the site. (#483) 2017-03-18 19:02:18 +00:00
logmanoriginal
155c0ac6f0 Merge branch 'ImproveBridge' of https://github.com/logmanoriginal/rss-bridge 2017-03-17 18:42:37 +01:00
logmanoriginal
596b9143a8 [TwitterBridge] Add option to hide retweets
Requested via #491
2017-03-17 18:41:35 +01:00
logmanoriginal
a2108c784f [FeedExpander] Properly cast simplexml elements
This fixes a possible cause of
"Serialization of 'SimpleXMLElement' is not allowed"
reported via #487
2017-03-13 22:12:11 +01:00
Teromene
c803396d7e Correct phpcs check. 2017-03-09 22:27:14 +00:00
Teromene
ac518ca297 Fix line return in user-agent. 2017-03-08 11:47:55 +00:00
Teromene
1763a1518c Restore the ability to whitelist all the bridges by putting a wildcard into the whitelist file. 2017-03-08 10:56:39 +00:00
Teromene
2dda74dfe7 Add contribution guidelines. 2017-03-07 11:46:54 +00:00
Teromene
b1c2a69102 Fix WebFailBridge (again).
It seems that they blacklist weird user agents, but not immediately. Switched to Firefox user agent, should stop causing problems.
2017-03-03 14:19:10 +00:00
Teromene
bf7ce98719 Fix VKBridge. 2017-03-03 14:14:05 +00:00
Teromene
8b2fdb3937 Add a function to convert the background-image attribute to an actual image. 2017-03-03 14:13:29 +00:00
Teromene
5d41a74067 Add WordPressPluginUpdateBridge.
Fix phpcs check in WebFailBridge.
2017-03-03 13:27:41 +00:00
Teromene
100f3cd56d Fix Webfailbridge, change the user-agent used for the request. 2017-03-03 12:10:23 +00:00
logmanoriginal
8f3c56b184 Merge branch 'ImproveCore' of https://github.com/logmanoriginal/rss-bridge 2017-02-18 13:48:15 +01:00
logmanoriginal
16bdf6b204 links: Rename defaultImageSrcTo to defaultLinkTo
This function not only fixes image sources, but also anchors
2017-02-18 13:41:45 +01:00
logmanoriginal
cf7da1d41c [html] Fix anchors after fixing images
Anchors will be fixed in a similar way as it is done with images,
so it can be done in one go.
2017-02-18 13:40:58 +01:00
logmanoriginal
bb8e7495d8 [html] Fix img src replacement not working
strpos returns false if the needle was not found. See:
http://php.net/manual/en/function.strpos.php#refsect1-function.strpos-returnvalues
2017-02-18 13:13:40 +01:00
logmanoriginal
5de03d6b9f [FileCache] Use serialize instead of json_encode
json_encode causes high memory footprint on large input data,
where serialize is less problematic.

Example: When using AcrimedBridge items contain pictures in
raw format (entire picture) which leads to a file size of about
2MB using serialize. json_encode will allocate about 98MB of
memory for encoding, causing memory exhausion errors (PHP
allows for 128MB of memory by default)
2017-02-18 12:54:26 +01:00
logmanoriginal
1d26c7f1c3 [FileCache] Do not delete .gitkeep
This commit reduces the chance of accidentally removing the cache
folder from repository.
2017-02-18 10:23:50 +01:00
logmanoriginal
790bd17d41 Merge branch 'MoinMoinBridge' of https://github.com/logmanoriginal/rss-bridge 2017-02-18 10:16:05 +01:00
logmanoriginal
1dcef02f27 [MoinMoinBridge] Add new bridge
This bridge returns feeds for each section (via given separator)
from a given MoinMoin compatible wiki.

The separator can be any tag of the following:
- h1
- h2
- h3
- li
- a

The number of items returned can be specified.
For anchor tags (a) the bridge can optionally follow the anchor to
the linked page and return it as content.
2017-02-18 03:13:20 +01:00
logmanoriginal
801ea837c9 Merge branch 'ImproveBridge' of https://github.com/logmanoriginal/rss-bridge 2017-02-17 20:17:57 +01:00
logmanoriginal
9124ed640e [WebfailBridge] Properly handle gifs (DOM changed) 2017-02-17 20:04:38 +01:00
logmanoriginal
6d1e8af982 Merge branch 'ImproveCore' of https://github.com/logmanoriginal/rss-bridge 2017-02-15 19:40:46 +01:00
logmanoriginal
512a4f292b bridges: Return parent::getURI by default 2017-02-15 19:38:32 +01:00
logmanoriginal
c4169f1579 bridges: Return parent::getName by default 2017-02-15 19:38:32 +01:00
logmanoriginal
d93d491d8e core: Use methods to access bridge information
Bridge information were exposed and accessed via public constants
which doesn't work if you want to generate bridges dynamically as
discussed in #402
2017-02-15 19:38:32 +01:00
logmanoriginal
c44fb25845 core: Improve documentation and style for BridgeAbstract and BridgeInterface
Public functions defined in BridgeAbstract also belong to BridgeInterface

getInput may only be used by this class or its children.
2017-02-15 19:36:29 +01:00
LogMANOriginal
761c66d813 Merge pull request #475 from LogMANOriginal/NewCodingStylePolicy
Apply coding style policy
2017-02-15 19:23:25 +01:00
logmanoriginal
ff83410534 style: Fix coding styles 2017-02-14 17:28:07 +01:00
logmanoriginal
d8f5aa3c79 [phpcs] Add sniffs for function declaration and -calls
When declaring a function
- Do not add a space before a comma
- Add a space after a comma
- Add a space after an equal sign

Example:
function myFunction($x, $y, $z = null){...}

When calling a function
- Do not add a space before the opening parenthesis
- Do not add a space after the opening parenthesis
- Do not add a space before the closing parenthesis
- Do not add a space before a comma
- Add a space after a comma

Example:
myFunction('x', 'y', 'z');
2017-02-14 17:03:05 +01:00
logmanoriginal
23430f1c07 [phpcs] Add documentation 2017-02-14 16:50:34 +01:00
logmanoriginal
0c3e58258c [MsnMondeBridge] Fix typo 2017-02-12 16:18:58 +01:00
logmanoriginal
b4f1dc35a1 [FB2Bridge] Split long lines
Splits long lines into short sections without using
string concatenation (.) to prevent errors due to
coding styles.
2017-02-12 15:34:08 +01:00
logmanoriginal
6f24858124 bridges: Fix coding styles
This commit is a squash of all commits that fix coding styles
for the new coding style policy.

[ABCTabsBridge] Fix coding style
[AcrimedBridge] Fix coding style
[AllocineFRBridge] Fix coding style
[AnimeUltimeBridge] Fix coding style
[Arte7Bridge] Fix coding style
[AskfmBridge] Fix coding style
[BandcampBridge] Fix coding style
[BastaBridge] Fix coding style
[BlaguesDeMerdeBridge] Fix coding style
[BooruprojectBridge] Fix coding style
[CADBridge] Fix coding style
[CNETBridge] Fix coding style
[CastorusBridge] Fix coding style
[CollegeDeFranceBridge] Fix coding style
[CommonDreamsBridge] Fix coding style
[CopieDoubleBridge] Fix coding style
[CourrierInternationalBridge] Fix coding style
[CpasbienBridge] Fix coding style
[CryptomeBridge] Fix coding style
[DailymotionBridge] Fix coding style
[DanbooruBridge] Fix coding style
[DansTonChatBridge] Fix coding style
[DauphineLibereBridge] Fix coding style
[DeveloppezDotComBridge] Fix coding style
[DemoBridge] Fix coding style
[DilbertBridge] Fix coding style
[DuckDuckGoBridge] Fix coding style
[DollbooruBridge] Fix coding style
[EliteDangerousGalnetBridge] Fix coding style
[ElsevierBridge] Fix coding style
[EstCeQuonMetEnProdBridge] Fix coding style
[EZTVBridge] Fix coding style
[FacebookBridge] Fix coding style
[FeedExpanderExampleBridge] Fix coding style
[FB2Bridge] Fix coding style
[FierPandaBridge] Fix coding style
[FlickrBridge] Fix coding style
[FootitoBridge] Fix coding style
[FourchanBridge] Fix coding style
[FuturaSciencesBridge] Fix coding style
[GBAtempBridge] Fix coding style
[GelbooruBridge] Fix coding style
[GiphyBridge] Fix coding style
[GithubIssueBridge] Fix coding style
[GizmodoBridge] Fix coding style
[GoComicsBridge] Fix coding style
[GooglePlusPostBridge] Fix coding style
[GoogleSearchBridge] Fix coding style
[HDWallpapersBridge] Fix coding style
[HentaiHavenBridge] Fix coding style
[IdenticaBridge] Fix coding style
[InstagramBridge] Fix coding style
[IsoHuntBridge] Fix coding style
[JapanExpoBridge] Fix coding style
[KonachanBridge] Fix coding style
[KoreusBridge] Fix coding style
[KununuBridge] Fix coding style
[LeBonCoinBridge] Fix coding style
[LegifranceJOBBridge] Fix coding style
[LeMondeInformatiqueBridge] Fix coding style
[LesJoiesDuCodeBridge] Fix coding style
[LichessBridge] Fix coding style
[LinkedInCompanyBridge] Fix coding style
[LolibooruBridge] Fix coding style
[LWNprevBridge] Fix coding style
[MangareaderBridge] Fix coding style
[MilbooruBridge] Fix coding style
[MixCloudBridge] Fix coding style
[MoebooruBridge] Fix coding style
[MondeDiploBridge] Fix coding style
[MsnMondeBridge] Fix coding style
[MspabooruBridge] Fix coding style
[NasaApodBridge] Fix coding style
[NeuviemeArtBridge] Fix coding style
[NextgovBridge] Fix coding style
[NextInpactBridge] Fix coding style
[NiceMatinBridge] Fix coding style
[NovelUpdatesBridge] Fix coding style
[OpenClassroomsBridge] Fix coding style
[ParuVenduImmoBridge] Fix coding style
[PickyWallpapersBridge] Fix coding style
[PinterestBridge] Fix coding style
[PlanetLibreBridge] Fix coding style
[ReadComicsBridge] Fix coding style
[Releases3DSBridge] Fix coding style
[ReporterreBridge] Fix coding style
[RTBFBridge] Fix coding style
[Rue89Bridge] Fix coding style
[Rule34Bridge] Fix coding style
[Rule34pahealBridge] Fix coding style
[SafebooruBridge] Fix coding style
[SakugabooruBridge] Fix coding style
[ScmbBridge] Fix coding style
[ScoopItBridge] Fix coding style
[SensCritiqueBridge] Fix coding style
[SexactuBridge] Fix coding style
[ShanaprojectBridge] Fix coding style
[Shimmie2Bridge] Fix coding style
[SoundcloudBridge] Fix coding style
[StripeAPIChangeLogBridge] Fix coding style
[SuperbWallpapersBridge] Fix coding style
[T411Bridge] Fix coding style
[TagBoardBridge] Fix coding style
[TbibBridge] Fix coding style
[TheCodingLoveBridge] Fix coding style
[TheHackerNewsBridge] Fix coding style
[ThePirateBayBridge] Fix coding style
[TheTVDBBridge] Fix coding style
[Torrent9Bridge] Fix coding style
[TwitterBridge] Fix coding style
[UnsplashBridge] Fix coding style
[ViadeoCompanyBridge] Fix coding style
[VineBridge] Fix coding style
[VkBridge] Fix coding style
[WallpaperStopBridge] Fix coding style
[WebfailBridge] Fix coding style
[WeLiveSecurityBridge] Fix coding style
[WhydBridge] Fix coding style
[WikipediaBridge] Fix coding style
[WordPressBridge] Fix coding style
[WorldOfTanksBridge] Fix coding style
[XbooruBridge] Fix coding style
[YandereBridge] Fix coding style
[YoutubeBridge] Fix coding style
[ZDNetBridge] Fix coding style
2017-02-12 15:34:08 +01:00
logmanoriginal
22a7666d2b [phpcs] Include bridges for coding style checks 2017-02-12 15:34:08 +01:00
logmanoriginal
04b885264d Merge branch 'FixBridges' of https://github.com/logmanoriginal/rss-bridge 2017-02-11 12:40:31 +01:00
logmanoriginal
37b5df8985 Remove FlickrExploreBridge and FlickrTagBridge
These bridges are replaced by the FlickrBridge
2017-02-11 12:38:32 +01:00
logmanoriginal
f16835c223 [FlickrBridge] Add new bridge
This bridge is a mashup of the existing FlickrExploreBridge by sebsauvage
and FlickrTagBridge by erwang. It provides the same functionality as one
single bridge.
2017-02-11 12:36:08 +01:00
logmanoriginal
7ad8693b5f [FlickrTagBridge] Fix and improve bridge by using the FlickrExploreBridge approach 2017-02-11 12:20:44 +01:00
logmanoriginal
0f25684e65 [FlickrExplore] Fix and improve bridge
Instead of utilizing API requests for each element, the information
is now read directly from the source page, which provides information
as JSON data embedded in a script block.

The author name is returned for each element.

Improves the title and optionally adds the description if available
2017-02-11 12:03:41 +01:00
Teromene
9bf74b2715 Added the alternate facebook bridge. 2017-02-08 11:21:59 +00:00
logmanoriginal
d91c25cff1 Merge branch 'KernelBugTrackerBridge' of https://github.com/logmanoriginal/rss-bridge 2017-02-07 21:37:19 +01:00
logmanoriginal
6ddcedb53f [KernelBugTracker] Add new bridge
This adds a bridge for bugzilla.kernel.org to provide feeds for
bug comments without the need of registering an email address.

This implementation makes use of the print preview feature that
reduces bandwidth by a small margin.

Provides options to specify the number of comments to return as
well as the sorting order (latest first or oldest first)
2017-02-07 21:29:15 +01:00
Astyan-42
a1764a9fe2 make the bridge compatible with wordpress with no static URL (#469) 2017-02-07 10:24:18 +00:00
Pellaeon Lin
1028e538ab Fix duplicate https://www.facebook.com/ on captcha_action POST URL (#466) 2017-02-02 16:18:23 +00:00
Pellaeon Lin
49cc0661ad Fix FacebookBridge feed name empty when data loaded from cache (#456) 2017-02-02 16:17:18 +00:00
Pellaeon Lin
3109694b1c Ignore summary posts generated by facebook (#467) 2017-02-02 16:13:26 +00:00
Astyan-42
aa0a84bc26 adding theTVDBBridge (#463)
* adding theTVDBBridge
2017-02-01 13:02:05 +00:00
Teromene
eb22f86f44 Merge pull request #455 from pellaeon/pr-1
Preserve br and p from Facebook post content
2017-02-01 10:22:42 +00:00
Teromene
cf909ef3a1 Merge pull request #465 from JeremyRand/duckduckgo-fix-redirect
[DuckDuckGoBridge] Disable redirects
2017-02-01 10:15:57 +00:00
JeremyRand
94d2ebec0a [DuckDuckGoBridge] Disable DuckDuckGo redirects so that the links returned are correct. 2017-02-01 00:58:05 +00:00
Mitsu
44c7cbe2d7 Merge pull request #458 from ldidry/gocomicsbridge
Add GoComicsBridge
2017-01-14 11:04:01 +01:00
Luc Didry
5b4ba621ee Add GoComicsBridge 2017-01-14 10:01:00 +01:00
Pellaeon Lin
9c1bedb33f Preserve br and p from Facebook post content 2017-01-13 06:38:05 +08:00
LogMANOriginal
2fd60c68b0 Merge pull request #452 from lagaisse/lagaisse
Add Torrent9bridge
2017-01-12 21:12:28 +01:00
Badet Aurélien
670d8f18cb [BridgeAbstract] Enable caching of extraInfos - Issue #431 (#434)
Enable caching of extraInfos.
2017-01-03 10:28:47 +00:00
Kevin Lagaisse
41714b4c40 Add Torrent9bridge
update cpasbienbridge
2017-01-01 12:05:37 +01:00
LogMANOriginal
a4f4447c5e Merge pull request #450 from ORelio/master
Update Futura-Sciences field retrieval
2016-12-19 20:17:20 +01:00
logmanoriginal
3d984e8762 Merge branch 'FixBridges' of https://github.com/logmanoriginal/rss-bridge 2016-12-19 20:09:28 +01:00
ORelio
3a6ccc4c29 Update Futura-Sciences field retrieval
See #433
2016-12-19 20:04:34 +01:00
logmanoriginal
f45405950d [GooglePlusPost] Trim unnecessary tags 2016-12-19 19:57:25 +01:00
logmanoriginal
0e5cf0d14e [GooglePlusPost] Return content and title compatible to current RSS-Bridge 2016-12-19 19:49:53 +01:00
logmanoriginal
9405dc6c4b Merge branch 'FixBridges' of https://github.com/logmanoriginal/rss-bridge 2016-12-17 18:11:59 +01:00
logmanoriginal
d0c9397613 [Kununu] Fix source layout changed 2016-12-17 18:04:21 +01:00
logmanoriginal
5ad3198d71 [Kununu] Fix content check condition
Check if null instead of false according to:
http://simplehtmldom.sourceforge.net/manual.htm#section_find
2016-12-17 17:21:29 +01:00
logmanoriginal
83b5bbcc37 [Kununu] Use tabs for indentation 2016-12-17 17:11:58 +01:00
logmanoriginal
f694023f7d bridges: Return default values for getName and getURI 2016-12-17 17:03:09 +01:00
logmanoriginal
61b9c3eb48 Merge branch 'FixBridges' of https://github.com/logmanoriginal/rss-bridge 2016-12-17 16:48:47 +01:00
logmanoriginal
d4fb02b0d0 bridges: Set missing MAINTAINER based on blame command
Maintainer should be set for all bridges. Using git blame to
determine who provided the most code to the files. This is
obviously not a good solution, feel free to insert own names
2016-12-17 16:43:47 +01:00
logmanoriginal
95b99d42a4 bridges: Fix default return values and function scopes
getURI and getName should fall back to parent::getURI or
parent::getName respectively if it cannot build propper
return values.

Functions defined by bridges should be made private to
prevent confusion with inherited functions
2016-12-17 16:39:18 +01:00
LogMANOriginal
271c71d0ac Merge pull request #444 from niawag/patch-3
[ThePirateBay] Filter results by user status (VIP/Trusted/None)
2016-12-16 18:22:33 +01:00
LogMANOriginal
1e0cef8f7f Merge pull request #448 from chemel/amazon
[AmazonBridge] Add possibility to choose Amazon location
2016-12-16 18:19:19 +01:00
niawag
8b52b3858e Filter results by user status (VIP/Trusted/None)
Took into account LogMANOriginal comment and tested it, everything's working.
2016-12-16 10:41:40 +01:00
logmanoriginal
4a1e5245b3 [contents] Don't suppress errors returned by file_get_contents 2016-12-15 20:24:34 +01:00
LogMANOriginal
cad78be37b Merge pull request #445 from da2x/patch-1
Set “Accept-Encoding: gzip” header
2016-12-14 12:57:48 +01:00
Alexis CHEMEL
a5b0e2a24f [AmazonBridge] Adding country list box #447 2016-12-13 17:02:49 +01:00
Teromene
4972cec951 Merge pull request #446 from TwizzyDizzy/master
Translate amazon bridge to english
2016-12-12 21:34:35 +00:00
Thomas Dalichow
f09e8e1139 Translate amazon bridge to english
Until now, it was my understanding to keep rss-bridge english, not french. Despite it's french roots.
2016-12-12 22:28:05 +01:00
LogMANOriginal
64fa134c40 Merge pull request #439 from chemel/travis
Fix errors returned by travis in BridgeAbstract
2016-12-12 17:02:57 +01:00
LogMANOriginal
d9030bfb97 [readme]
Add travis build Status image
2016-12-12 17:02:05 +01:00
Daniel Aleksandersen
278d6a0ec2 Set “Accept-Encoding: gzip” header 2016-12-12 11:29:34 +01:00
niawag
8bb002c7b6 Filter results by user status (VIP/Trusted/None) 2016-12-11 12:11:03 +01:00
Teromene
b4e6c0d973 Merge pull request #443 from EtienneM/patch-2
Fix SCMB bridge
2016-12-10 15:26:33 +00:00
Étienne
1ef7e40ecd Fix SCMB bridge
This trailing `/` made the link to each article invalid
2016-12-10 15:56:30 +01:00
Teromene
00403214ce Merge pull request #441 from chemel/amazon-bridge
Amazon Bridge
2016-12-10 14:13:10 +00:00
Teromene
9c65c7b9e1 Merge pull request #442 from chemel/google
GoogleSearchBridge Fix
2016-12-10 14:12:37 +00:00
Alexis CHEMEL
877465d508 AmazonBridge 2016-12-10 14:58:48 +01:00
Alexis CHEMEL
35415004b9 Fix GoogleSearchBridge
find div instead li
2016-12-10 14:56:25 +01:00
Alexis CHEMEL
e908fe648b T411Bridge Minor fixes (#438)
T411Bridge Minor fixes
2016-12-06 17:20:41 +00:00
Alexis CHEMEL
3f503c4356 MixCloudBridge #436 (#437)
MixCloudBridge #436
FuturaSciencesBridge #433
2016-12-06 11:12:42 +00:00
Alexis CHEMEL
f4aa3b39e8 fix BridgeAbstract 2016-12-06 01:01:07 +01:00
Badet Aurélien
c702a0e69f Bridge getExtraInfos (#432)
* add function getExtraInfos() to BridgeAbstract

* replace call to $bridge->getName() and $bridge->getURI() by $bridge->getExtraInfos()

replace call to $bridge->getName() and $bridge->getURI() by $bridge->getExtraInfos() defined by default in BridgeAbstract.
So we could pass additionals ExtraInfos from custom bridges to custom formats.
2016-11-29 00:48:59 +00:00
logmanoriginal
5edba3a1aa Merge branch 'MultipleEnclosures' of https://github.com/logmanoriginal/rss-bridge 2016-11-16 16:55:14 +01:00
logmanoriginal
8d41718553 [HtmlFormat] Update stylesheet for attachments 2016-11-16 16:27:17 +01:00
logmanoriginal
72f40fbd75 [formats] Allow multiple enclosures
All formats now support multiple enclosures. RSS
will show a warning if more than one enclosure
is used since many feed reader don't support
multiple enclosures with RSS (also not clearly
specified in the specification)
2016-11-12 22:04:42 +01:00
logmanoriginal
14c689e7a3 [core] Fix typos 2016-11-09 19:10:40 +01:00
logmanoriginal
84bc9d2da6 Merge branch 'ImproveFormat' of https://github.com/logmanoriginal/rss-bridge 2016-11-09 19:01:17 +01:00
logmanoriginal
42cbc2e889 [formats] Fix enclosures
All bridges failed due to missing 'enclosures' element in
the items array. With this commit all formats (ATOM, RSS
and HTML) provide support for a single 'enclosure' element
2016-11-09 18:59:17 +01:00
logmanoriginal
3a2cb9ea1e [formats] Use custom characterset in all formats
The specified characterset will now apply to all formats
thus allowing other charactersets than 'UTF-8'
2016-11-09 18:43:06 +01:00
logmanoriginal
4f4fb11789 [FormatAbstract] Allow child classes to overwrite DEFAULT_CHARSET
By using 'static' instead of 'self' the constant may
be overridden by child classes.
2016-11-09 18:41:25 +01:00
logmanoriginal
28e813620f [FormatInterface] Add missing public functions
This commit adds all missing functions to the interface
that are defined and implemented as public functions in
FormatAbstract.
2016-11-09 18:41:24 +01:00
LogMANOriginal
fdf98041e3 Merge pull request #428 from mro/feature/xml_escape
CDATA considered harmful.
2016-11-09 18:35:52 +01:00
LogMANOriginal
29e64f77aa Merge pull request #430 from sysadminstory/master
Fix Cache write verification
2016-11-09 18:26:54 +01:00
Gilles Maurer
4dfbc16a5b Fix Cache write verification
PHP operator '===' is the only strict way to mix up the value '0' and
the value 'FALSE'.

The function saveData of the FileCache tests if the write of the cache
files was done with success and raise an Exception if not. The test was
done without the '===' operator, and if the data is 0 bytes long the
error message says there is a permission error, which is false.

A data 0 bytes long is another issue, either in the json_encode function
either in the Bridge, but not a permission issue.
2016-11-09 02:11:22 +01:00
Marcus Rohrmoser
af572341b3 Fix to PR #427 (#429)
* Ouch, sorry.

* fix indentation.
2016-11-08 21:44:54 +00:00
Teromene
51e9298a2b Merge pull request #427 from mro/feature/enclosure
Revive enclosures.
2016-11-08 21:24:44 +00:00
Marcus Rohrmoser
6df657179f CDATA considered harmful.
https://evertpot.com/184/
2016-11-08 22:21:48 +01:00
Marcus Rohrmoser
2ff422d312 Revive enclosures.Reverts 0663c95. Refs #198 #175. 2016-11-08 22:03:51 +01:00
Teromene
1b3efce64d Merge pull request #426 from mro/language
don’t lie about the language.
2016-11-08 00:33:17 +00:00
Marcus Rohrmoser
750812c512 don’t lie about the language. 2016-11-07 23:29:44 +01:00
256 changed files with 29167 additions and 8103 deletions

7
.dockerignore Normal file
View File

@@ -0,0 +1,7 @@
.git
cache/*
DEBUG
Dockerfile
whitelist.txt
phpcs.xml
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.
-->

6
.gitignore vendored
View File

@@ -227,8 +227,12 @@ pip-log.txt
/cache
/whitelist.txt
DEBUG
config.ini.php
######################
## VisualStudioCode ##
######################
.vscode/*
.vscode/*
#Builder
.buildconfig

View File

@@ -1,19 +1,44 @@
dist: trusty
language: php
php:
- '5.6'
- '7.0'
- hhvm
- nightly
install:
- 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
- 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:
fast_finish: true
include:
- php: 5.6
- php: 7.0
- php: nightly
allow_failures:
- php: hhvm
- php: nightly
- php: nightly

View File

@@ -1,163 +0,0 @@
rss-bridge Changelog
===
Alpha 0.1
===
* First tagged version.
* Includes refactoring.
* Unstable.
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

5
Dockerfile Normal file
View File

@@ -0,0 +1,5 @@
FROM ulsmith/alpine-apache-php7
COPY ./ /app/public/
RUN chown -R apache:root /app/public

270
README.md
View File

@@ -1,130 +1,224 @@
rss-bridge
===
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE)
[![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)
===
* `FlickrExplore` : [Latest interesting images](http://www.flickr.com/explore) from Flickr
* `GoogleSearch` : Most recent results from Google Search
* `GooglePlus` : Most recent posts of user timeline
* `Twitter` : Return keyword/hashtag search or user timeline
* `Identi.ca` : Identica user timeline (Should be compatible with other Pump.io instances)
* `YouTube` : YouTube user channel, playlist or search
* `Cryptome` : Returns the most recent documents from [Cryptome.org](http://cryptome.org/)
* `DansTonChat`: Most recent quotes from [danstonchat.com](http://danstonchat.com/)
* `DuckDuckGo`: Most recent results from [DuckDuckGo.com](https://duckduckgo.com/)
* `Instagram`: Most recent photos from an Instagram user
* `OpenClassrooms`: Lastest tutorials from [fr.openclassrooms.com](http://fr.openclassrooms.com/)
* `Pinterest`: Most recent photos from user or search
* `ScmbBridge`: Newest stories from [secouchermoinsbete.fr](http://secouchermoinsbete.fr/)
* `Wikipedia`: highlighted articles from [Wikipedia](https://wikipedia.org/) in English, German, French or Esperanto
* `Bandcamp` : Returns last release from [bandcamp](https://bandcamp.com/) for a tag
* `ThePirateBay` : Returns the newest indexed torrents from [The Pirate Bay](https://thepiratebay.se/) with keywords
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/)
* `Bandcamp` : Returns last release from [bandcamp](https://bandcamp.com/) for a tag
* `Cryptome` : Returns the most recent documents from [Cryptome.org](http://cryptome.org/)
* `DansTonChat`: Most recent quotes from [danstonchat.com](http://danstonchat.com/)
* `DuckDuckGo`: Most recent results from [DuckDuckGo.com](https://duckduckgo.com/)
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/)
* `FlickrExplore` : [Latest interesting images](http://www.flickr.com/explore) from Flickr
* `GooglePlus` : Most recent posts of user timeline
* `GoogleSearch` : Most recent results from Google Search
* `Identi.ca` : Identica user timeline (Should be compatible with other Pump.io instances)
* `Instagram`: Most recent photos from an Instagram user
* `OpenClassrooms`: Lastest tutorials from [fr.openclassrooms.com](http://fr.openclassrooms.com/)
* `Pinterest`: Most recent photos from user or search
* `ScmbBridge`: Newest stories from [secouchermoinsbete.fr](http://secouchermoinsbete.fr/)
* `ThePirateBay` : Returns the newest indexed torrents from [The Pirate Bay](https://thepiratebay.se/) with keywords
* `Twitter` : Return keyword/hashtag search or user timeline
* `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
* `Mrss` : MRSS Feed, for use in RSS/Feed readers
* `Json` : Json, for consumption by other applications.
* `Html` : Simple html page.
* `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`)
* `allow_url_fopen=1` in `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

@@ -1,25 +1,42 @@
<?php
class ABCTabsBridge extends BridgeAbstract{
class ABCTabsBridge extends BridgeAbstract {
const MAINTAINER = "kranack";
const NAME = "ABC Tabs Bridge";
const URI = "http://www.abc-tabs.com/";
const DESCRIPTION = "Returns 22 newest tabs";
const MAINTAINER = 'kranack';
const NAME = 'ABC Tabs Bridge';
const URI = 'https://www.abc-tabs.com/';
const DESCRIPTION = 'Returns 22 newest tabs';
public function collectData(){
$html = '';
$html = getSimpleHTMLDOM(static::URI.'tablatures/nouveautes.html') or returnClientError('No results for this query.');
$html = getSimpleHTMLDOM(static::URI . 'tablatures/nouveautes.html')
or returnClientError('No results for this query.');
$table = $html->find('table#myTable', 0)->children(1);
foreach ($table->find('tr') as $tab)
{
$item = array();
$item['author'] = $tab->find('td', 1)->plaintext . ' - ' . $tab->find('td', 2)->plaintext;
$item['title'] = $tab->find('td', 1)->plaintext . ' - ' . $tab->find('td', 2)->plaintext;
$item['content'] = 'Le ' . $tab->find('td', 0)->plaintext . '<br> Par: ' . $tab->find('td', 5)->plaintext . '<br> Type: ' . $tab->find('td', 3)->plaintext;
$item['id'] = static::URI . $tab->find('td', 2)->find('a', 0)->getAttribute('href');
$item['uri'] = static::URI . $tab->find('td', 2)->find('a', 0)->getAttribute('href');
$this->items[] = $item;
foreach ($table->find('tr') as $tab) {
$item = array();
$item['author'] = $tab->find('td', 1)->plaintext
. ' - '
. $tab->find('td', 2)->plaintext;
$item['title'] = $tab->find('td', 1)->plaintext
. ' - '
. $tab->find('td', 2)->plaintext;
$item['content'] = 'Le '
. $tab->find('td', 0)->plaintext
. '<br> Par: '
. $tab->find('td', 5)->plaintext
. '<br> Type: '
. $tab->find('td', 3)->plaintext;
$item['id'] = static::URI
. $tab->find('td', 2)->find('a', 0)->getAttribute('href');
$item['uri'] = static::URI
. $tab->find('td', 2)->find('a', 0)->getAttribute('href');
$this->items[] = $item;
}
}
}
}

View File

@@ -1,25 +1,24 @@
<?php
class AcrimedBridge extends FeedExpander {
const MAINTAINER = "qwertygc";
const NAME = "Acrimed Bridge";
const URI = "http://www.acrimed.org/";
const CACHE_TIMEOUT = 4800; //2hours
const DESCRIPTION = "Returns the newest articles.";
const MAINTAINER = 'qwertygc';
const NAME = 'Acrimed Bridge';
const URI = 'http://www.acrimed.org/';
const CACHE_TIMEOUT = 4800; //2hours
const DESCRIPTION = 'Returns the newest articles';
public function collectData(){
$this->collectExpandableDatas(static::URI.'spip.php?page=backend');
}
public function collectData(){
$this->collectExpandableDatas(static::URI . 'spip.php?page=backend');
}
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
$articlePage = getSimpleHTMLDOM($newsItem->link);
$article = sanitize($articlePage->find('article.article1', 0)->innertext);
$article = defaultImageSrcTo($article, static::URI);
$item['content'] = $article;
return $item;
}
$articlePage = getSimpleHTMLDOM($newsItem->link);
$article = sanitize($articlePage->find('article.article1', 0)->innertext);
$article = defaultLinkTo($article, static::URI);
$item['content'] = $article;
return $item;
}
}

View File

@@ -1,82 +1,86 @@
<?php
class AllocineFRBridge extends BridgeAbstract{
class AllocineFRBridge extends BridgeAbstract {
const MAINTAINER = 'superbaillot.net';
const NAME = 'Allo Cine Bridge';
const CACHE_TIMEOUT = 25200; // 7h
const URI = 'http://www.allocine.fr/';
const DESCRIPTION = 'Bridge for allocine.fr';
const PARAMETERS = array( array(
'category' => array(
'name' => 'category',
'type' => 'list',
'required' => true,
'exampleValue' => 'Faux Raccord',
'title' => 'Select your category',
'values' => array(
'Faux Raccord' => 'faux-raccord',
'Top 5' => 'top-5',
'Tueurs en Séries' => 'tueurs-en-serie'
)
)
));
const MAINTAINER = "superbaillot.net";
const NAME = "Allo Cine Bridge";
const CACHE_TIMEOUT = 25200; // 7h
const URI = "http://www.allocine.fr/";
const DESCRIPTION = "Bridge for allocine.fr";
const PARAMETERS = array( array(
'category'=>array(
'name'=>'category',
'type'=>'list',
'required'=>true,
'exampleValue'=>'Faux Raccord',
'title'=>'Select your category',
'values'=>array(
'Faux Raccord'=>'faux-raccord',
'Top 5'=>'top-5',
'Tueurs en Séries'=>'tueurs-en-serie'
)
)
));
public function getURI(){
if(!is_null($this->getInput('category'))) {
public function getURI(){
switch($this->getInput('category')){
case 'faux-raccord':
$uri = static::URI.'video/programme-12284/saison-27129/';
break;
case 'top-5':
$uri = static::URI.'video/programme-12299/saison-29561/';
break;
case 'tueurs-en-serie':
$uri = static::URI.'video/programme-12286/saison-22938/';
break;
}
switch($this->getInput('category')) {
case 'faux-raccord':
$uri = static::URI . 'video/programme-12284/saison-32180/';
break;
case 'top-5':
$uri = static::URI . 'video/programme-12299/saison-29561/';
break;
case 'tueurs-en-serie':
$uri = static::URI . 'video/programme-12286/saison-22938/';
break;
}
return $uri;
}
return $uri;
}
public function getName(){
return self::NAME.' : '
.array_search(
$this->getInput('category'),
self::PARAMETERS[$this->queriedContext]['category']['values']
);
}
return parent::getURI();
}
public function collectData(){
public function getName(){
if(!is_null($this->getInput('category'))) {
return self::NAME . ' : '
. array_search(
$this->getInput('category'),
self::PARAMETERS[$this->queriedContext]['category']['values']
);
}
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError("Could not request ".$this->getURI()." !");
return parent::getName();
}
$category=array_search(
$this->getInput('category'),
self::PARAMETERS[$this->queriedContext]['category']['values']
);
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request ' . $this->getURI() . ' !');
foreach($html->find('figure.media-meta-fig') as $element)
{
$item = array();
$category = array_search(
$this->getInput('category'),
self::PARAMETERS[$this->queriedContext]['category']['values']
);
$title = $element->find('div.titlebar h3.title a', 0);
$content = trim($element->innertext);
$figCaption = strpos($content, $category);
foreach($html->find('.media-meta-list figure.media-meta-fig') as $element) {
$item = array();
if($figCaption !== false)
{
$content = str_replace('src="/', 'src="'.static::URI, $content);
$content = str_replace('href="/', 'href="'.static::URI, $content);
$content = str_replace('src=\'/', 'src=\''.static::URI, $content);
$content = str_replace('href=\'/', 'href=\''.static::URI, $content);
$item['content'] = $content;
$item['title'] = trim($title->innertext);
$item['uri'] = static::URI . $title->href;
$this->items[] = $item;
}
}
}
$title = $element->find('div.titlebar h3.title a', 0);
$content = trim($element->innertext);
$figCaption = strpos($content, $category);
if($figCaption !== false) {
$content = str_replace('src="/', 'src="' . static::URI, $content);
$content = str_replace('href="/', 'href="' . static::URI, $content);
$content = str_replace('src=\'/', 'src=\'' . static::URI, $content);
$content = str_replace('href=\'/', 'href=\'' . static::URI, $content);
$item['content'] = $content;
$item['title'] = trim($title->innertext);
$item['uri'] = static::URI . $title->href;
$this->items[] = $item;
}
}
}
}

97
bridges/AmazonBridge.php Normal file
View File

@@ -0,0 +1,97 @@
<?php
class AmazonBridge extends BridgeAbstract {
const MAINTAINER = 'Alexis CHEMEL';
const NAME = 'Amazon';
const URI = 'https://www.amazon.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns products from Amazon search';
const PARAMETERS = array(array(
'q' => array(
'name' => 'Keyword',
'required' => true,
),
'sort' => array(
'name' => 'Sort by',
'type' => 'list',
'required' => false,
'values' => array(
'Relevance' => 'relevanceblender',
'Price: Low to High' => 'price-asc-rank',
'Price: High to Low' => 'price-desc-rank',
'Average Customer Review' => 'review-rank',
'Newest Arrivals' => 'date-desc-rank',
),
'defaultValue' => 'relevanceblender',
),
'tld' => array(
'name' => 'Country',
'type' => 'list',
'required' => true,
'values' => array(
'Australia' => 'com.au',
'Brazil' => 'com.br',
'Canada' => 'ca',
'China' => 'cn',
'France' => 'fr',
'Germany' => 'de',
'India' => 'in',
'Italy' => 'it',
'Japan' => 'co.jp',
'Mexico' => 'com.mx',
'Netherlands' => 'nl',
'Spain' => 'es',
'United Kingdom' => 'co.uk',
'United States' => 'com',
),
'defaultValue' => 'com',
),
));
public function getName(){
if(!is_null($this->getInput('tld')) && !is_null($this->getInput('q'))) {
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('q');
}
return parent::getName();
}
public function collectData() {
$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.');
foreach($html->find('li.s-result-item') as $element) {
$item = array();
// Title
$title = $element->find('h2', 0);
if (is_null($title)) {
continue;
}
$item['title'] = html_entity_decode($title->innertext, ENT_QUOTES);
// Url
$uri = $title->parent()->getAttribute('href');
$uri = substr($uri, 0, strrpos($uri, '/'));
$item['uri'] = substr($uri, 0, strrpos($uri, '/'));
// Content
$image = $element->find('img', 0);
$price = $element->find('span.s-price', 0);
$price = ($price) ? $price->innertext : '';
$item['content'] = '<img src="' . $image->getAttribute('src') . '" /><br />' . $price;
$this->items[] = $item;
}
}
}

View File

@@ -0,0 +1,187 @@
<?php
class AmazonPriceTrackerBridge extends BridgeAbstract {
const MAINTAINER = 'captn3m0';
const NAME = 'Amazon Price Tracker';
const URI = 'https://www.amazon.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Tracks price for a single product on Amazon';
const PARAMETERS = array(
array(
'asin' => array(
'name' => 'ASIN',
'required' => true,
'exampleValue' => 'B071GB1VMQ',
// https://stackoverflow.com/a/12827734
'pattern' => 'B[\dA-Z]{9}|\d{9}(X|\d)',
),
'tld' => array(
'name' => 'Country',
'type' => 'list',
'required' => true,
'values' => array(
'Australia' => 'com.au',
'Brazil' => 'com.br',
'Canada' => 'ca',
'China' => 'cn',
'France' => 'fr',
'Germany' => 'de',
'India' => 'in',
'Italy' => 'it',
'Japan' => 'co.jp',
'Mexico' => 'com.mx',
'Netherlands' => 'nl',
'Spain' => 'es',
'United Kingdom' => 'co.uk',
'United States' => 'com',
),
'defaultValue' => 'com',
),
));
protected $title;
/**
* Generates domain name given a amazon TLD
*/
private function getDomainName() {
return 'https://www.amazon.' . $this->getInput('tld');
}
/**
* Generates URI for a Amazon product page
*/
public function getURI() {
if (!is_null($this->getInput('asin'))) {
return $this->getDomainName() . '/dp/' . $this->getInput('asin') . '/';
}
return parent::getURI();
}
/**
* Scrapes the product title from the html page
* returns the default title if scraping fails
*/
private function getTitle($html) {
$titleTag = $html->find('#productTitle', 0);
if (!$titleTag) {
return $this->getDefaultTitle();
} else {
return trim(html_entity_decode($titleTag->innertext, ENT_QUOTES));
}
}
/**
* Title used by the feed if none could be found
*/
private function getDefaultTitle() {
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('asin');
}
/**
* Returns name for the feed
* Uses title (already scraped) if it has one
*/
public function getName() {
if (isset($this->title)) {
return $this->title;
} else {
return parent::getName();
}
}
private function parseDynamicImage($attribute) {
$json = json_decode(html_entity_decode($attribute), true);
if ($json and count($json) > 0) {
return array_keys($json)[0];
}
}
/**
* Returns a generated image tag for the product
*/
private function getImage($html) {
$imageSrc = $html->find('#main-image-container img', 0);
if ($imageSrc) {
$hiresImage = $imageSrc->getAttribute('data-old-hires');
$dynamicImageAttribute = $imageSrc->getAttribute('data-a-dynamic-image');
$image = $hiresImage ?: $this->parseDynamicImage($dynamicImageAttribute);
}
$image = $image ?: 'https://placekitten.com/200/300';
return <<<EOT
<img width="300" style="max-width:300;max-height:300" src="$image" alt="{$this->title}" />
EOT;
}
/**
* Return \simple_html_dom object
* for the entire html of the product page
*/
private function getHtml() {
$uri = $this->getURI();
return getSimpleHTMLDOM($uri) ?: returnServerError('Could not request Amazon.');
}
private function scrapePriceFromMetrics($html) {
$asinData = $html->find('#cerberus-data-metrics', 0);
// <div id="cerberus-data-metrics" style="display: none;"
// data-asin="B00WTHJ5SU" data-asin-price="14.99" data-asin-shipping="0"
// data-asin-currency-code="USD" data-substitute-count="-1" ... />
if ($asinData) {
return [
'price' => $asinData->getAttribute('data-asin-price'),
'currency' => $asinData->getAttribute('data-asin-currency-code'),
'shipping' => $asinData->getAttribute('data-asin-shipping')
];
}
return false;
}
private function scrapePriceGeneric($html) {
$priceDiv = $html->find('span.offer-price', 0) ?: $html->find('.a-color-price', 0);
preg_match('/^\s*([A-Z]{3}|£|\$)\s?([\d.,]+)\s*$/', $priceDiv->plaintext, $matches);
if (count($matches) === 3) {
return [
'price' => $matches[2],
'currency' => $matches[1],
'shipping' => '0'
];
}
return false;
}
/**
* Scrape method for Amazon product page
* @return [type] [description]
*/
public function collectData() {
$html = $this->getHtml();
$this->title = $this->getTitle($html);
$imageTag = $this->getImage($html);
$data = $this->scrapePriceFromMetrics($html) ?: $this->scrapePriceGeneric($html);
$item = array(
'title' => $this->title,
'uri' => $this->getURI(),
'content' => "$imageTag<br/>Price: {$data['price']} {$data['currency']}",
);
if ($data['shipping'] !== '0') {
$item['content'] .= "<br>Shipping: {$data['shipping']} {$data['currency']}</br>";
}
$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

@@ -1,119 +1,140 @@
<?php
class AnimeUltimeBridge extends BridgeAbstract {
const MAINTAINER = 'ORelio';
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 PARAMETERS = array( array(
'type'=>array(
'name'=>'Type',
'type'=>'list',
'values'=>array(
'Everything'=>'',
'Anime'=>'A',
'Drama'=>'D',
'Tokusatsu'=>'T'
)
)
));
const MAINTAINER = 'ORelio';
const NAME = 'Anime-Ultime';
const URI = 'http://www.anime-ultime.net/';
const CACHE_TIMEOUT = 10800; // 3h
const DESCRIPTION = 'Returns the newest releases posted on Anime-Ultime.';
const PARAMETERS = array( array(
'type' => array(
'name' => 'Type',
'type' => 'list',
'values' => array(
'Everything' => '',
'Anime' => 'A',
'Drama' => 'D',
'Tokusatsu' => 'T'
)
)
));
private $filter = 'Releases';
private $filter = 'Releases';
public function collectData(){
public function collectData(){
//Add type filter if provided
$typeFilter = array_search(
$this->getInput('type'),
self::PARAMETERS[$this->queriedContext]['type']['values']
);
//Add type filter if provided
$typeFilter = array_search(
$this->getInput('type'),
self::PARAMETERS[$this->queriedContext]['type']['values']
);
//Build date and filters for making requests
$thismonth = date('mY').$typeFilter;
$lastmonth = date('mY', mktime(0, 0, 0, date('n') - 1, 1, date('Y'))).$typeFilter;
//Build date and filters for making requests
$thismonth = date('mY') . $typeFilter;
$lastmonth = date('mY', mktime(0, 0, 0, date('n') - 1, 1, date('Y'))) . $typeFilter;
//Process each HTML page until having 10 releases
$processedOK = 0;
foreach (array($thismonth, $lastmonth) as $requestFilter) {
//Process each HTML page until having 10 releases
$processedOK = 0;
foreach (array($thismonth, $lastmonth) as $requestFilter) {
//Retrive page contents
$url = self::URI.'history-0-1/'.$requestFilter;
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request Anime-Ultime: '.$url);
//Retrive page contents
$url = self::URI . 'history-0-1/' . $requestFilter;
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request Anime-Ultime: ' . $url);
//Relases are sorted by day : process each day individually
foreach ($html->find('div.history', 0)->find('h3') as $daySection) {
//Relases are sorted by day : process each day individually
foreach($html->find('div.history', 0)->find('h3') as $daySection) {
//Retrieve day and build date information
$dateString = $daySection->plaintext;
$day = intval(substr($dateString, strpos($dateString, ' ') + 1, 2));
$item_date = strtotime(str_pad($day, 2, '0', STR_PAD_LEFT).'-'
.substr($requestFilter, 0, 2).'-'
.substr($requestFilter, 2, 4));
$release = $daySection->next_sibling()->next_sibling()->first_child(); //<h3>day</h3><br /><table><tr> <-- useful data in table rows
//Retrieve day and build date information
$dateString = $daySection->plaintext;
$day = intval(substr($dateString, strpos($dateString, ' ') + 1, 2));
$item_date = strtotime(str_pad($day, 2, '0', STR_PAD_LEFT)
. '-'
. substr($requestFilter, 0, 2)
. '-'
. substr($requestFilter, 2, 4));
//Process each release of that day, ignoring first table row: contains table headers
while (!is_null($release = $release->next_sibling())) {
if (count($release->find('td')) > 0) {
//<h3>day</h3><br /><table><tr> <-- useful data in table rows
$release = $daySection->next_sibling()->next_sibling()->first_child();
//Retrieve metadata from table columns
$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_episode = html_entity_decode(str_pad($release->find('td', 1)->plaintext, 2, '0', STR_PAD_LEFT));
$item_fansub = $release->find('td', 2)->plaintext;
$item_type = $release->find('td', 4)->plaintext;
//Process each release of that day, ignoring first table row: contains table headers
while(!is_null($release = $release->next_sibling())) {
if(count($release->find('td')) > 0) {
if (!empty($item_uri)) {
//Retrieve metadata from table columns
$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);
//Retrieve description from description page and convert relative image src info absolute image src
$html_item = getContents($item_uri)
or returnServerError('Could not request Anime-Ultime: '.$item_uri);
$item_description = substr(
$html_item,
strpos($html_item, 'class="principal_contain" align="center">')
+ 41
);
$item_description = substr($item_description,
0,
strpos($item_description, '<div id="table">')
);
$item_description = str_replace(
'src="images', 'src="'.self::URI.'images',
$item_description
);
$item_description = str_replace("\r", '', $item_description);
$item_description = str_replace("\n", '', $item_description);
$item_description = utf8_encode($item_description);
$item_image = self::URI . substr(
$item_link_element->onmouseover,
37,
strpos($item_link_element->onmouseover, ' ', 37) - 37
);
//Build and add final item
$item = array();
$item['uri'] = $item_uri;
$item['title'] = $item_name.' '.$item_type.' '.$item_episode;
$item['author'] = $item_fansub;
$item['timestamp'] = $item_date;
$item['content'] = $item_description;
$this->items[] = $item;
$processedOK++;
$item_episode = html_entity_decode(
str_pad(
$release->find('td', 1)->plaintext,
2,
'0',
STR_PAD_LEFT
)
);
//Stop processing once limit is reached
if ($processedOK >= 10)
return;
}
}
}
}
}
}
$item_fansub = $release->find('td', 2)->plaintext;
$item_type = $release->find('td', 4)->plaintext;
public function getName() {
$typeFilter = array_search(
$this->getInput('type'),
self::PARAMETERS[$this->queriedContext]['type']['values']
);
if(!empty($item_uri)) {
return 'Latest '.$typeFilter.' - Anime-Ultime Bridge';
}
// Retrieve description from description page
$html_item = getContents($item_uri)
or returnServerError('Could not request Anime-Ultime: ' . $item_uri);
$item_description = substr(
$html_item,
strpos($html_item, 'class="principal_contain" align="center">') + 41
);
$item_description = substr($item_description,
0,
strpos($item_description, '<div id="table">')
);
// 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);
//Build and add final item
$item = array();
$item['uri'] = $item_uri;
$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++;
//Stop processing once limit is reached
if ($processedOK >= 10)
return;
}
}
}
}
}
}
public function getName() {
if(!is_null($this->getInput('type'))) {
$typeFilter = array_search(
$this->getInput('type'),
self::PARAMETERS[$this->queriedContext]['type']['values']
);
return 'Latest ' . $typeFilter . ' - Anime-Ultime Bridge';
}
return parent::getName();
}
}

View File

@@ -1,91 +1,122 @@
<?php
class Arte7Bridge extends BridgeAbstract{
class Arte7Bridge extends BridgeAbstract {
const MAINTAINER = "mitsukarenai";
const NAME = "Arte +7";
const URI = "http://www.arte.tv/";
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = "Returns newest videos from ARTE +7";
const PARAMETERS = array(
'Catégorie (Français)' => array(
'catfr'=>array(
'type'=>'list',
'name'=>'Catégorie',
'values'=>array(
'Toutes les vidéos (français)'=>'toutes-les-videos',
'Actu & société'=>'actu-société',
'Séries & fiction'=>'séries-fiction',
'Cinéma'=>'cinéma',
'Arts & spectacles classiques'=>'arts-spectacles-classiques',
'Culture pop'=>'culture-pop',
'Découverte'=>'découverte',
'Histoire'=>'histoire',
'Junior'=>'junior'
const MAINTAINER = 'mitsukarenai';
const NAME = 'Arte +7';
const URI = 'https://www.arte.tv/';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = 'Returns newest videos from ARTE +7';
)
)
),
'Catégorie (Allemand)' => array(
'catde'=>array(
'type'=>'list',
'name'=>'Catégorie',
'values'=>array(
'Alle Videos (deutsch)'=>'alle-videos',
'Aktuelles & Gesellschaft'=>'aktuelles-gesellschaft',
'Fernsehfilme & Serien'=>'fernsehfilme-serien',
'Kino'=>'kino',
'Kunst & Kultur'=>'kunst-kultur',
'Popkultur & Alternativ'=>'popkultur-alternativ',
'Entdeckung'=>'entdeckung',
'Geschichte'=>'geschichte',
'Junior'=>'junior'
)
)
)
);
const API_TOKEN = 'Nzc1Yjc1ZjJkYjk1NWFhN2I2MWEwMmRlMzAzNjI5NmU3NWU3ODg4ODJjOWMxNTMxYzEzZGRjYjg2ZGE4MmIwOA';
public function collectData(){
const PARAMETERS = array(
'Catégorie (Français)' => array(
'catfr' => array(
'type' => 'list',
'name' => 'Catégorie',
'values' => array(
'Toutes les vidéos (français)' => null,
'Actu & société' => 'ACT',
'Séries & fiction' => 'SER',
'Cinéma' => 'CIN',
'Arts & spectacles classiques' => 'ARS',
'Culture pop' => 'CPO',
'Découverte' => 'DEC',
'Histoire' => 'HIST',
'Science' => 'SCI',
'Autre' => 'AUT'
)
)
),
'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',
'name' => 'Catégorie',
'values' => array(
'Alle Videos (deutsch)' => null,
'Aktuelles & Gesellschaft' => 'ACT',
'Fernsehfilme & Serien' => 'SER',
'Kino' => 'CIN',
'Kunst & Kultur' => 'ARS',
'Popkultur & Alternativ' => 'CPO',
'Entdeckung' => 'DEC',
'Geschichte' => 'HIST',
'Wissenschaft' => 'SCI',
'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/'
)
)
);
switch($this->queriedContext){
case 'Catégorie (Français)':
$category=$this->getInput('catfr');
$lang='fr';
break;
case 'Catégorie (Allemand)':
$category=$this->getInput('catde');
$lang='de';
break;
}
public function collectData(){
switch($this->queriedContext) {
case 'Catégorie (Français)':
$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 = self::URI.'guide/'.$lang.'/plus7/'.$category;
$input = getContents($url) or die('Could not request ARTE.');
if(strpos($input, 'categoryVideoSet') !== FALSE){
$input = explode('categoryVideoSet="', $input);
$input = explode('}}', $input[1]);
$input = $input[0].'}}';
}else{
$input = explode('videoSet="', $input);
$input = explode('}]}', $input[1]);
$input = $input[0].'}]}';
}
$url = 'https://api.arte.tv/api/opa/v3/videos?sort=-lastModified&limit=10&language='
. $lang
. ($category != null ? '&category.code=' . $category : '')
. ($collectionId != null ? '&collections.collectionId=' . $collectionId : '');
$input_json = json_decode(html_entity_decode($input, ENT_QUOTES), TRUE);
$header = array(
'Authorization: Bearer ' . self::API_TOKEN
);
foreach($input_json['videos'] as $element) {
$item = array();
$item['uri'] = str_replace("autoplay=1", "", $element['url']);
$item['id'] = $element['id'];
$hack_broadcast_time = $element['rights_end'];
$hack_broadcast_time = strtok($hack_broadcast_time, 'T');
$hack_broadcast_time = strtok('T');
$item['timestamp'] = strtotime($element['scheduled_on'].'T'.$hack_broadcast_time);
$item['title'] = $element['title'];
if (!empty($element['subtitle']))
$item['title'] = $element['title'].' | '.$element['subtitle'];
$item['duration'] = round((int)$element['duration']/60);
$item['content'] = $element['teaser'].'<br><br>'.$item['duration'].'min<br><a href="'.$item['uri'].'"><img src="' . $element['thumbnail_url'] . '" /></a>';
$this->items[] = $item;
}
}
$input = getContents($url, $header) or die('Could not request ARTE.');
$input_json = json_decode($input, true);
foreach($input_json['videos'] as $element) {
$item = array();
$item['uri'] = $element['url'];
$item['id'] = $element['id'];
$item['timestamp'] = strtotime($element['videoRightsBegin']);
$item['title'] = $element['title'];
if(!empty($element['subtitle']))
$item['title'] = $element['title'] . ' | ' . $element['subtitle'];
$item['duration'] = round((int)$element['durationSeconds'] / 60);
$item['content'] = $element['teaserText']
. '<br><br>'
. $item['duration']
. 'min<br><a href="'
. $item['uri']
. '"><img src="'
. $element['mainImage']['url']
. '" /></a>';
$this->items[] = $item;
}
}
}

View File

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

@@ -1,43 +1,67 @@
<?php
class BandcampBridge extends BridgeAbstract{
class BandcampBridge extends BridgeAbstract {
const MAINTAINER = "sebsauvage";
const NAME = "Bandcamp Tag";
const URI = "http://bandcamp.com/";
const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = "New bandcamp release by tag";
const PARAMETERS = array( array(
'tag'=>array(
'name'=>'tag',
'type'=>'text',
'required'=>true
)
));
const MAINTAINER = 'sebsauvage';
const NAME = 'Bandcamp Tag';
const URI = 'https://bandcamp.com/';
const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = 'New bandcamp release by tag';
const PARAMETERS = array( array(
'tag' => array(
'name' => 'tag',
'type' => 'text',
'required' => true
)
));
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('No results for this query.');
public function getIcon() {
return 'https://s4.bcbits.com/img/bc_favicon.ico';
}
foreach($html->find('li.item') as $release) {
$script = $release->find('div.art', 0)->getAttribute('onclick');
$uri = ltrim($script, "return 'url(");
$uri = rtrim($uri, "')");
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('No results for this query.');
$item = array();
$item['author'] = $release->find('div.itemsubtext',0)->plaintext . ' - ' . $release->find('div.itemtext',0)->plaintext;
$item['title'] = $release->find('div.itemsubtext',0)->plaintext . ' - ' . $release->find('div.itemtext',0)->plaintext;
$item['content'] = '<img src="' . $uri . '"/><br/>' . $release->find('div.itemsubtext',0)->plaintext . ' - ' . $release->find('div.itemtext',0)->plaintext;
$item['id'] = $release->find('a',0)->getAttribute('href');
$item['uri'] = $release->find('a',0)->getAttribute('href');
$this->items[] = $item;
}
}
foreach($html->find('li.item') as $release) {
$script = $release->find('div.art', 0)->getAttribute('onclick');
$uri = ltrim($script, "return 'url(");
$uri = rtrim($uri, "')");
public function getURI(){
return self::URI.'tag/'.urlencode($this->getInput('tag')).'?sort_field=date';
}
$item = array();
$item['author'] = $release->find('div.itemsubtext', 0)->plaintext
. ' - '
. $release->find('div.itemtext', 0)->plaintext;
public function getName(){
return $this->getInput('tag') .' - '.'Bandcamp Tag';
}
$item['title'] = $release->find('div.itemsubtext', 0)->plaintext
. ' - '
. $release->find('div.itemtext', 0)->plaintext;
$item['content'] = '<img src="'
. $uri
. '"/><br/>'
. $release->find('div.itemsubtext', 0)->plaintext
. ' - '
. $release->find('div.itemtext', 0)->plaintext;
$item['id'] = $release->find('a', 0)->getAttribute('href');
$item['uri'] = $release->find('a', 0)->getAttribute('href');
$this->items[] = $item;
}
}
public function getURI(){
if(!is_null($this->getInput('tag'))) {
return self::URI . 'tag/' . urlencode($this->getInput('tag')) . '?sort_field=date';
}
return parent::getURI();
}
public function getName(){
if(!is_null($this->getInput('tag'))) {
return $this->getInput('tag') . ' - Bandcamp Tag';
}
return parent::getName();
}
}

View File

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

View File

@@ -1,33 +1,46 @@
<?php
class BlaguesDeMerdeBridge extends BridgeAbstract{
class BlaguesDeMerdeBridge extends BridgeAbstract {
const MAINTAINER = "superbaillot.net";
const NAME = "Blagues De Merde";
const URI = "http://www.blaguesdemerde.fr/";
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = "Blagues De Merde";
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.');
public function collectData(){
foreach($html->find('article.joke_contener') as $element) {
$item = array();
$temp = $element->find('a');
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;
}
}
}
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request BDM.');
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;
}
}
}
?>

View File

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

View File

@@ -1,36 +1,45 @@
<?php
require_once('GelbooruBridge.php');
class BooruprojectBridge extends GelbooruBridge{
class BooruprojectBridge extends GelbooruBridge {
const MAINTAINER = "mitsukarenai";
const NAME = "Booruproject";
const URI = "http://booru.org/";
const DESCRIPTION = "Returns images from given page of booruproject";
const MAINTAINER = 'mitsukarenai';
const NAME = 'Booruproject';
const URI = 'http://booru.org/';
const DESCRIPTION = 'Returns images from given page of booruproject';
const PARAMETERS = array(
'global' => array(
'p' => array(
'name' => 'page',
'type' => 'number'
),
't' => array(
'name' => 'tags'
)
),
'Booru subdomain (subdomain.booru.org)' => array(
'i' => array(
'name' => 'Subdomain',
'required' => true
)
)
);
const PARAMETERS = array(
'global'=>array(
'p'=>array(
'name'=>'page',
'type'=>'number'
),
't'=>array('name'=>'tags')
),
'Booru subdomain (subdomain.booru.org)'=>array(
'i'=>array(
'name'=>'Subdomain',
'required'=>true
)
)
);
const PIDBYPAGE = 20;
const PIDBYPAGE=20;
public function getURI(){
if(!is_null($this->getInput('i'))) {
return 'http://' . $this->getInput('i') . '.booru.org/';
}
public function getURI(){
return 'http://'.$this->getInput('i').'.booru.org/';
}
return parent::getURI();
}
public function getName(){
return static::NAME . ' ' . $this->getInput('i');
}
public function getName(){
if(!is_null($this->getInput('i'))) {
return static::NAME . ' ' . $this->getInput('i');
}
return parent::getName();
}
}

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,46 +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->CADExtractContent($item['uri']);
return $item;
}
private function CADExtractContent($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

@@ -1,76 +1,109 @@
<?php
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 MAINTAINER = 'ORelio';
const NAME = 'CNET News';
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'
)
)
)
);
const PARAMETERS = array( array(
'topic'=>array('name'=>'Topic name')
));
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;
}
public function collectData(){
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;
}
// 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);
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;
}
// Retrieve webpage
$pageUrl = self::URI . (empty($topic) ? 'news/' : $topic . '/');
$html = getSimpleHTMLDOM($pageUrl)
or returnServerError('Could not request CNET: ' . $pageUrl);
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;
}
// Process articles
foreach($html->find('div.assetBody, div.riverPost') as $element) {
$pageUrl = self::URI.(empty($this->getInput('topic')) ? '' : 'topics/'.$this->getInput('topic').'/');
$html = getSimpleHTMLDOM($pageUrl) or returnServerError('Could not request CNET: '.$pageUrl);
$limit = 0;
if(count($this->items) >= 10) {
break;
}
foreach($html->find('div.assetBody') as $element) {
if ($limit < 8) {
$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>';
$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 (is_null($article_thumbnail))
$article_thumbnail = extractFromDelimiters($element->innertext, '<img src="', '"');
if (!empty($article_title) && !empty($article_uri) && strpos($article_uri, '/news/') !== false) {
if (!empty($article_title) && !empty($article_uri) && strpos($article_uri, self::URI . 'news/') !== false) {
$article_html = getSimpleHTMLDOM($article_uri) or returnServerError('Could not request CNET: '.$article_uri);
$article_html = getSimpleHTMLDOMCached($article_uri) or $article_html = null;
$article_content = trim(CleanArticle(ExtractFromDelimiters($article_html, '<div class="articleContent', '<footer>')));
if (!is_null($article_html)) {
$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++;
}
}
}
}
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;
public function getName() {
$topic=$this->getInput('topic');
return 'CNET News Bridge'.(empty($topic) ? '' : ' - '.$topic);
}
$article_content .= trim(
$this->cleanArticle(
extractFromDelimiters(
$article_html, '<article', '<footer'
)
)
);
}
$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;
}
}
}
}

View File

@@ -1,35 +1,35 @@
<?php
class CastorusBridge extends BridgeAbstract {
const MAINTAINER = "logmanoriginal";
const NAME = "Castorus Bridge";
const MAINTAINER = 'logmanoriginal';
const NAME = 'Castorus Bridge';
const URI = 'http://www.castorus.com';
const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = "Returns the latest changes";
const DESCRIPTION = 'Returns the latest changes';
const PARAMETERS = array(
'Get latest changes' => array(),
'Get latest changes via ZIP code' => array(
'zip'=>array(
'name'=>'ZIP code',
'type'=>'text',
'required'=>true,
'exampleValue'=>'74910, 74',
'title'=>'Insert ZIP code (complete or partial)'
)
),
'Get latest changes via city name' => array(
'city'=>array(
'name'=>'City name',
'type'=>'text',
'required'=>true,
'exampleValue'=>'Seyssel, Seys',
'title'=>'Insert city name (complete or partial)'
)
)
);
const PARAMETERS = array(
'Get latest changes' => array(),
'Get latest changes via ZIP code' => array(
'zip' => array(
'name' => 'ZIP code',
'type' => 'text',
'required' => true,
'exampleValue' => '74910, 74',
'title' => 'Insert ZIP code (complete or partial)'
)
),
'Get latest changes via city name' => array(
'city' => array(
'name' => 'City name',
'type' => 'text',
'required' => true,
'exampleValue' => 'Seyssel, Seys',
'title' => 'Insert city name (complete or partial)'
)
)
);
// Extracts the tile from an actitiy
private function ExtractActivityTitle($activity){
// Extracts the title from an actitiy
private function extractActivityTitle($activity){
$title = $activity->find('a', 0);
if(!$title)
@@ -39,7 +39,7 @@ class CastorusBridge extends BridgeAbstract {
}
// Extracts the url from an actitiy
private function ExtractActivityUrl($activity){
private function extractActivityUrl($activity){
$url = $activity->find('a', 0);
if(!$url)
@@ -49,7 +49,7 @@ class CastorusBridge extends BridgeAbstract {
}
// Extracts the time from an activity
private function ExtractActivityTime($activity){
private function extractActivityTime($activity){
// Unfortunately the time is part of the parent node,
// so we have to clear all child nodes first
$nodes = $activity->find('*');
@@ -57,7 +57,7 @@ class CastorusBridge extends BridgeAbstract {
if(!$nodes)
returnServerError('Cannot find nodes!');
foreach($nodes as $node){
foreach($nodes as $node) {
$node->outertext = '';
}
@@ -65,7 +65,7 @@ class CastorusBridge extends BridgeAbstract {
}
// Extracts the price change
private function ExtractActivityPrice($activity){
private function extractActivityPrice($activity){
$price = $activity->find('span', 1);
if(!$price)
@@ -75,8 +75,8 @@ class CastorusBridge extends BridgeAbstract {
}
public function collectData(){
$zip_filter = trim($this->getInput('zip'));
$city_filter = trim($this->getInput('city'));
$zip_filter = trim($this->getInput('zip'));
$city_filter = trim($this->getInput('city'));
$html = getSimpleHTMLDOM(self::URI);
@@ -88,20 +88,27 @@ class CastorusBridge extends BridgeAbstract {
if(!$activities)
returnServerError('Failed to find activities!');
foreach($activities as $activity){
foreach($activities as $activity) {
$item = array();
$item['title'] = $this->ExtractActivityTitle($activity);
$item['uri'] = $this->ExtractActivityUrl($activity);
$item['timestamp'] = $this->ExtractActivityTime($activity);
$item['content'] = '<a href="' . $item['uri'] . '">' . $item['title'] . '</a><br><p>'
. $this->ExtractActivityPrice($activity) . '</p>';
$item['title'] = $this->extractActivityTitle($activity);
$item['uri'] = $this->extractActivityUrl($activity);
$item['timestamp'] = $this->extractActivityTime($activity);
$item['content'] = '<a href="'
. $item['uri']
. '">'
. $item['title']
. '</a><br><p>'
. $this->extractActivityPrice($activity)
. '</p>';
if(isset($zip_filter) && !(substr($item['title'], 0, strlen($zip_filter)) === $zip_filter)){
if(isset($zip_filter)
&& !(substr($item['title'], 0, strlen($zip_filter)) === $zip_filter)) {
continue; // Skip this item
}
if(isset($city_filter) && !(substr($item['title'], strpos($item['title'], ' ') + 1, strlen($city_filter)) === $city_filter)){
if(isset($city_filter)
&& !(substr($item['title'], strpos($item['title'], ' ') + 1, strlen($city_filter)) === $city_filter)) {
continue; // Skip this item
}

View File

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

View File

@@ -1,11 +1,11 @@
<?php
class CollegeDeFranceBridge extends BridgeAbstract{
class CollegeDeFranceBridge extends BridgeAbstract {
const MAINTAINER = "pit-fgfjiudghdf";
const NAME = "CollegeDeFrance";
const URI = "http://www.college-de-france.fr/";
const MAINTAINER = 'pit-fgfjiudghdf';
const NAME = 'CollegeDeFrance';
const URI = 'http://www.college-de-france.fr/';
const CACHE_TIMEOUT = 10800; // 3h
const DESCRIPTION = "Returns the latest audio and video from CollegeDeFrance";
const DESCRIPTION = 'Returns the latest audio and video from CollegeDeFrance';
public function collectData(){
$months = array(
@@ -22,47 +22,61 @@ class CollegeDeFranceBridge extends BridgeAbstract{
'11' => 'nov.',
'12' => 'déc.'
);
// The "API" used by the site returns a list of partial HTML in this form
/* <li>
* <a href="/site/thomas-romer/guestlecturer-2016-04-15-14h30.htm" data-target="after">
* <span class="date"><span class="list-icon list-icon-video"></span><span class="list-icon list-icon-audio"></span>15 avr. 2016</span>
* <span class="date"><span class="list-icon list-icon-video"></span>
* <span class="list-icon list-icon-audio"></span>15 avr. 2016</span>
* <span class="lecturer">Christopher Hays</span>
* <span class='title'>Imagery of Divine Suckling in the Hebrew Bible and the Ancient Near East</span>
* </a>
* </li>
*/
$html = getSimpleHTMLDOM(self::URI.'components/search-audiovideo.jsp?fulltext=&siteid=1156951719600&lang=FR&type=all')
or returnServerError('Could not request CollegeDeFrance.');
$html = getSimpleHTMLDOM(self::URI
. 'components/search-audiovideo.jsp?fulltext=&siteid=1156951719600&lang=FR&type=all')
or returnServerError('Could not request CollegeDeFrance.');
foreach($html->find('a[data-target]') as $element) {
$item = array();
$item['title'] = $element->find('.title', 0)->plaintext;
// Most relative URLs contains an hour in addition to the date, so let's use it
// <a href="/site/yann-lecun/course-2016-04-08-11h00.htm" data-target="after">
//
// Sometimes there's an __1, perhaps it signifies an update "/site/patrick-boucheron/seminar-2016-05-03-18h00__1.htm"
// Sometimes there's an __1, perhaps it signifies an update
// "/site/patrick-boucheron/seminar-2016-05-03-18h00__1.htm"
//
// But unfortunately some don't have any hours info
// <a href="/site/institut-physique/The-Mysteries-of-Decoherence-Sebastien-Gleyzes-[Video-3-35].htm" data-target="after">
// <a href="/site/institut-physique/
// The-Mysteries-of-Decoherence-Sebastien-Gleyzes-[Video-3-35].htm" data-target="after">
$timezone = new DateTimeZone('Europe/Paris');
// strpos($element->href, '201') will break in 2020 but it'll probably break prior to then due to site changes anyway
$d = DateTime::createFromFormat(
'!Y-m-d-H\hi',
substr($element->href, strpos($element->href, '201'), 16),
$timezone
);
if(!$d){
$d=DateTime::createFromFormat(
'!d m Y',
trim(str_replace(
array_values($months),
array_keys($months),
$element->find('.date', 0)->plaintext
)),
$timezone
);
}
$item['timestamp'] = $d->format('U');
$item['content'] = $element->find('.lecturer', 0)->innertext . ' - ' . $element->find('.title', 0)->innertext;
// strpos($element->href, '201') will break in 2020 but it'll
// probably break prior to then due to site changes anyway
$d = DateTime::createFromFormat(
'!Y-m-d-H\hi',
substr($element->href, strpos($element->href, '201'), 16),
$timezone
);
if(!$d) {
$d = DateTime::createFromFormat(
'!d m Y',
trim(str_replace(
array_values($months),
array_keys($months),
$element->find('.date', 0)->plaintext
)),
$timezone
);
}
$item['timestamp'] = $d->format('U');
$item['content'] = $element->find('.lecturer', 0)->innertext
. ' - '
. $element->find('.title', 0)->innertext;
$item['uri'] = self::URI . $element->href;
$this->items[] = $item;
}

View File

@@ -1,10 +1,10 @@
<?php
class CommonDreamsBridge extends FeedExpander {
const MAINTAINER = "nyutag";
const NAME = "CommonDreams Bridge";
const URI = "http://www.commondreams.org/";
const DESCRIPTION = "Returns the newest articles.";
const MAINTAINER = 'nyutag';
const NAME = 'CommonDreams Bridge';
const URI = 'https://www.commondreams.org/';
const DESCRIPTION = 'Returns the newest articles.';
public function collectData(){
$this->collectExpandableDatas('http://www.commondreams.org/rss.xml', 10);
@@ -12,11 +12,11 @@ class CommonDreamsBridge extends FeedExpander {
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
$item['content'] = $this->CommonDreamsExtractContent($item['uri']);
$item['content'] = $this->extractContent($item['uri']);
return $item;
}
private function CommonDreamsExtractContent($url) {
private function extractContent($url){
$html3 = getSimpleHTMLDOMCached($url);
$text = $html3->find('div[class=field--type-text-with-summary]', 0)->innertext;
$html3->clear();

View File

@@ -0,0 +1,97 @@
<?php
class ContainerLinuxReleasesBridge extends BridgeAbstract {
const MAINTAINER = 'captn3m0';
const NAME = 'Core OS Container Linux Releases Bridge';
const URI = 'https://coreos.com/releases/';
const DESCRIPTION = 'Returns the releases notes for Container Linux';
const STABLE = 'stable';
const BETA = 'beta';
const ALPHA = 'alpha';
const PARAMETERS = [
[
'channel' => [
'name' => 'Release Channel',
'type' => 'list',
'required' => true,
'defaultValue' => self::STABLE,
'values' => [
'Stable' => self::STABLE,
'Beta' => self::BETA,
'Alpha' => self::ALPHA,
],
]
]
];
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());
foreach ($data as $releaseVersion => $release) {
$item = [];
$item['uri'] = "https://coreos.com/releases/#$releaseVersion";
$item['title'] = $releaseVersion;
$content = $release['release_notes'];
$content .= <<<EOT
Major Software:
* Kernel: {$release['major_software']['kernel'][0]}
* Docker: {$release['major_software']['docker'][0]}
* etcd: {$release['major_software']['etcd'][0]}
EOT;
$item['timestamp'] = strtotime($release['release_date']);
// Based on https://gist.github.com/jbroadway/2836900
// Links
$regex = '/\[([^\[]+)\]\(([^\)]+)\)/';
$replacement = '<a href=\'\2\'>\1</a>';
$item['content'] = preg_replace($regex, $replacement, $content);
// Headings
$regex = '/^(.*)\:\s?$/m';
$replacement = '<h3>\1</h3>';
$item['content'] = preg_replace($regex, $replacement, $item['content']);
// Lists
$regex = '/\n\s*[\*|\-](.*)/';
$item['content'] = preg_replace_callback ($regex, function($regs) {
$item = $regs[1];
return sprintf ('<ul><li>%s</li></ul>', trim ($item));
}, $item['content']);
$this->items[] = $item;
}
}
private function getJsonUri() {
$channel = $this->getInput('channel');
return "https://coreos.com/releases/releases-$channel.json";
}
public function getURI() {
return self::URI;
}
public function getName(){
if(!is_null($this->getInput('channel'))) {
return 'Container Linux Releases: ' . $this->getInput('channel') . ' Channel';
}
return parent::getName();
}
}

View File

@@ -1,39 +1,35 @@
<?php
class CopieDoubleBridge extends BridgeAbstract{
class CopieDoubleBridge extends BridgeAbstract {
const MAINTAINER = "superbaillot.net";
const NAME = "CopieDouble";
const URI = "http://www.copie-double.com/";
const CACHE_TIMEOUT = 14400; // 4h
const DESCRIPTION = "CopieDouble";
const MAINTAINER = 'superbaillot.net';
const NAME = 'CopieDouble';
const URI = 'http://www.copie-double.com/';
const CACHE_TIMEOUT = 14400; // 4h
const DESCRIPTION = 'CopieDouble';
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request CopieDouble.');
$table = $html->find('table table', 2);
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request CopieDouble.');
foreach($table->find('tr') as $element)
{
$td = $element->find('td', 0);
if($td->class == "couleur_1")
{
$item = array();
$table = $html->find('table table', 2);
$title = $td->innertext;
$pos = strpos($title, "<a");
$title = substr($title, 0, $pos);
$item['title'] = $title;
}
elseif(strpos($element->innertext, "/images/suivant.gif") === false)
{
$a=$element->find("a", 0);
$item['uri'] = self::URI . $a->href;
foreach($table->find('tr') as $element) {
$td = $element->find('td', 0);
$content = str_replace('src="/', 'src="/'.self::URI,$element->find("td", 0)->innertext);
$content = str_replace('href="/', 'href="'.self::URI,$content);
$item['content'] = $content;
$this->items[] = $item;
}
}
}
if($td->class === 'couleur_1') {
$item = array();
$title = $td->innertext;
$pos = strpos($title, '<a');
$title = substr($title, 0, $pos);
$item['title'] = $title;
} elseif(strpos($element->innertext, '/images/suivant.gif') === false) {
$a = $element->find('a', 0);
$item['uri'] = self::URI . $a->href;
$content = str_replace('src="/', 'src="/' . self::URI, $element->find('td', 0)->innertext);
$content = str_replace('href="/', 'href="' . self::URI, $content);
$item['content'] = $content;
$this->items[] = $item;
}
}
}
}

View File

@@ -1,61 +1,55 @@
<?php
class CourrierInternationalBridge extends BridgeAbstract{
class CourrierInternationalBridge extends BridgeAbstract {
const MAINTAINER = "teromene";
const NAME = "Courrier International Bridge";
const URI = "http://CourrierInternational.com/";
const CACHE_TIMEOUT = 300; // 5 min
const DESCRIPTION = "Courrier International bridge";
const MAINTAINER = 'teromene';
const NAME = 'Courrier International Bridge';
const URI = 'http://CourrierInternational.com/';
const CACHE_TIMEOUT = 300; // 5 min
const DESCRIPTION = 'Courrier International bridge';
public function collectData(){
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Error.');
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Error.');
$element = $html->find('article');
$article_count = 1;
$element = $html->find("article");
foreach($element as $article) {
$item = array();
$article_count = 1;
$item['uri'] = $article->parent->getAttribute('href');
foreach($element as $article) {
if(strpos($item['uri'], 'http') === false) {
$item['uri'] = self::URI . $item['uri'];
}
$item = array();
$page = getSimpleHTMLDOMCached($item['uri']);
$item['uri'] = $article->parent->getAttribute("href");
$content = $page->find('.article-text', 0);
if(strpos($item['uri'], "http") === FALSE) {
$item['uri'] = self::URI.$item['uri'];
}
if(!$content) {
$content = $page->find('.depeche-text', 0);
}
$page = getSimpleHTMLDOMCached($item['uri']);
$item['content'] = sanitize($content);
$item['title'] = strip_tags($article->find('.title', 0));
$content = $page->find('.article-text',0);
if(!$content){
$content = $page->find('.depeche-text',0);
}
$dateTime = date_parse($page->find('time', 0));
$item['content'] = sanitize($content);
$item['title'] = strip_tags($article->find(".title",0));
$item['timestamp'] = mktime(
$dateTime['hour'],
$dateTime['minute'],
$dateTime['second'],
$dateTime['month'],
$dateTime['day'],
$dateTime['year']
);
$dateTime = date_parse($page->find("time",0));
$this->items[] = $item;
$article_count ++;
$item['timestamp'] = mktime(
$dateTime['hour'],
$dateTime['minute'],
$dateTime['second'],
$dateTime['month'],
$dateTime['day'],
$dateTime['year']
);
$this->items[] = $item;
$article_count ++;
if($article_count > 5) break;
}
}
if($article_count > 5)
break;
}
}
}
?>

View File

@@ -1,53 +0,0 @@
<?php
class CpasbienBridge extends BridgeAbstract {
const MAINTAINER = "lagaisse";
const NAME = "Cpasbien Bridge";
const URI = "http://www.cpasbien.io";
const CACHE_TIMEOUT = 86400; // 24h
const DESCRIPTION = "Returns latest torrents from a request query";
const PARAMETERS = array( array(
'q'=>array(
'name'=>'Search',
'required'=>true,
'title'=>'Type your search'
)
));
public function collectData(){
$request = str_replace(" ","-",trim($this->getInput('q')));
$html = getSimpleHTMLDOM(self::URI.'/recherche/'.urlencode($request).'.html')
or returnServerError('No results for this query.');
foreach ($html->find('#gauche',0)->find('div') as $episode) {
if ($episode->getAttribute('class')=='ligne0' ||
$episode->getAttribute('class')=='ligne1')
{
$htmlepisode=getSimpleHTMLDOMCached($episode->find('a', 0)->getAttribute('href'));
$item = array();
$item['author'] = $episode->find('a', 0)->text();
$item['title'] = $episode->find('a', 0)->text();
$textefiche=$htmlepisode->find('#textefiche', 0)->find('p',1);
if (isset($textefiche)) {
$item['content'] = $textefiche->text();
} else {
$p=$htmlepisode->find('#textefiche',0)->find('p');
if(!empty($p)){
$item['content'] = $htmlepisode->find('#textefiche', 0)->find('p',0)->text();
}
}
$item['id'] = $episode->find('a', 0)->getAttribute('href');
$item['uri'] = self::URI . $htmlepisode->find('#telecharger',0)->getAttribute('href');
$this->items[] = $item;
}
}
}
public function getName(){
return $this->getInput('q').' : '.self::NAME;
}
}

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

@@ -1,39 +1,45 @@
<?php
class CryptomeBridge extends BridgeAbstract{
class CryptomeBridge extends BridgeAbstract {
const MAINTAINER = "BoboTiG";
const NAME = "Cryptome";
const URI = "https://cryptome.org/";
const CACHE_TIMEOUT = 21600; //6h
const DESCRIPTION = "Returns the N most recent documents.";
const MAINTAINER = 'BoboTiG';
const NAME = 'Cryptome';
const URI = 'https://cryptome.org/';
const CACHE_TIMEOUT = 21600; //6h
const DESCRIPTION = 'Returns the N most recent documents.';
const PARAMETERS = array( array(
'n'=>array(
'name'=>'number of elements',
'type'=>'number',
'defaultValue'=>20,
'exampleValue'=>10
)
));
const PARAMETERS = array( array(
'n' => array(
'name' => 'number of elements',
'type' => 'number',
'defaultValue' => 20,
'exampleValue' => 10
)
));
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request Cryptome.');
$number=$this->getInput('n');
if (!empty($number)) { /* number of documents */
$num = min($number, 20);
}
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request Cryptome.');
$number = $this->getInput('n');
foreach($html->find('pre') as $element) {
for ( $i = 0; $i < $num; ++$i ) {
$item = array();
$item['uri'] = self::URI.substr($element->find('a', $i)->href, 20);
$item['title'] = substr($element->find('b', $i)->plaintext, 22);
$item['content'] = preg_replace('#http://cryptome.org/#', self::URI, $element->find('b', $i)->innertext);
$this->items[] = $item;
}
break;
}
}
/* number of documents */
if(!empty($number)) {
$num = min($number, 20);
}
foreach($html->find('pre') as $element) {
for($i = 0; $i < $num; ++$i) {
$item = array();
$item['uri'] = self::URI . substr($element->find('a', $i)->href, 20);
$item['title'] = substr($element->find('b', $i)->plaintext, 22);
$item['content'] = preg_replace(
'#http://cryptome.org/#',
self::URI,
$element->find('b', $i)->innertext
);
$this->items[] = $item;
}
break;
}
}
}

View File

@@ -1,114 +1,127 @@
<?php
class DailymotionBridge extends BridgeAbstract{
class DailymotionBridge extends BridgeAbstract {
const MAINTAINER = "mitsukarenai";
const NAME = "Dailymotion Bridge";
const URI = "https://www.dailymotion.com/";
const CACHE_TIMEOUT = 10800; // 3h
const DESCRIPTION = "Returns the 5 newest videos by username/playlist or search";
const MAINTAINER = 'mitsukarenai';
const NAME = 'Dailymotion Bridge';
const URI = 'https://www.dailymotion.com/';
const CACHE_TIMEOUT = 10800; // 3h
const DESCRIPTION = 'Returns the 5 newest videos by username/playlist or search';
const PARAMETERS = array (
'By username' => array(
'u'=>array(
'name'=>'username',
'required'=>true
)
),
const PARAMETERS = array (
'By username' => array(
'u' => array(
'name' => 'username',
'required' => true
)
),
'By playlist id' => array(
'p' => array(
'name' => 'playlist id',
'required' => true
)
),
'From search results' => array(
's' => array(
'name' => 'Search keyword',
'required' => true
),
'pa' => array(
'name' => 'Page',
'type' => 'number'
)
)
);
'By playlist id' => array(
'p'=>array(
'name'=>'playlist id',
'required'=>true
)
),
protected function getMetadata($id){
$metadata = array();
$html2 = getSimpleHTMLDOM(self::URI . 'video/' . $id);
if(!$html2) {
return $metadata;
}
'From search results' => array(
's'=>array(
'name'=>'Search keyword',
'required'=>true
),
'pa'=>array(
'name'=>'Page',
'type'=>'number'
)
)
);
$metadata['title'] = $html2->find('meta[property=og:title]', 0)->getAttribute('content');
$metadata['timestamp'] = strtotime(
$html2->find('meta[property=video:release_date]', 0)->getAttribute('content')
);
$metadata['thumbnailUri'] = $html2->find('meta[property=og:image]', 0)->getAttribute('content');
$metadata['uri'] = $html2->find('meta[property=og:url]', 0)->getAttribute('content');
return $metadata;
}
function getMetadata($id) {
$metadata=array();
$html2 = getSimpleHTMLDOM(self::URI.'video/'.$id);
if(!$html2){
return $metadata;
}
public function getIcon() {
return 'https://static1-ssl.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vf806ca4ed0deed812';
}
$metadata['title'] = $html2->find('meta[property=og:title]', 0)->getAttribute('content');
$metadata['timestamp'] = strtotime($html2->find('meta[property=video:release_date]', 0)->getAttribute('content') );
$metadata['thumbnailUri'] = $html2->find('meta[property=og:image]', 0)->getAttribute('content');
$metadata['uri'] = $html2->find('meta[property=og:url]', 0)->getAttribute('content');
return $metadata;
}
public function collectData(){
$html = '';
$limit = 5;
$count = 0;
public function collectData(){
$html = '';
$limit = 5;
$count = 0;
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request Dailymotion.');
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request Dailymotion.');
foreach($html->find('div.media a.preview_link') as $element) {
if($count < $limit) {
$item = array();
$item['id'] = str_replace('/video/', '', strtok($element->href, '_'));
$metadata = $this->getMetadata($item['id']);
if(empty($metadata)) {
continue;
}
$item['uri'] = $metadata['uri'];
$item['title'] = $metadata['title'];
$item['timestamp'] = $metadata['timestamp'];
foreach($html->find('div.media a.preview_link') as $element) {
if($count < $limit) {
$item = array();
$item['id'] = str_replace('/video/', '', strtok($element->href, '_'));
$metadata = $this->getMetadata($item['id']);
if(empty($metadata)){
continue;
}
$item['uri'] = $metadata['uri'];
$item['title'] = $metadata['title'];
$item['timestamp'] = $metadata['timestamp'];
$item['content'] = '<a href="' . $item['uri'] . '"><img src="' . $metadata['thumbnailUri'] . '" /></a><br><a href="' . $item['uri'] . '">' . $item['title'] . '</a>';
$this->items[] = $item;
$count++;
}
}
}
$item['content'] = '<a href="'
. $item['uri']
. '"><img src="'
. $metadata['thumbnailUri']
. '" /></a><br><a href="'
. $item['uri']
. '">'
. $item['title']
. '</a>';
public function getName(){
switch($this->queriedContext){
case 'By username':
$specific=$this->getInput('u');
break;
case 'By playlist id':
$specific=strtok($this->getInput('p'), '_');
break;
case 'From search results':
$specific=$this->getInput('s');
break;
}
$this->items[] = $item;
$count++;
}
}
}
return $specific.' : Dailymotion Bridge';
}
public function getName(){
switch($this->queriedContext) {
case 'By username':
$specific = $this->getInput('u');
break;
case 'By playlist id':
$specific = strtok($this->getInput('p'), '_');
break;
case 'From search results':
$specific = $this->getInput('s');
break;
default: return parent::getName();
}
public function getURI(){
$uri=self::URI;
switch($this->queriedContext){
case 'By username':
$uri.='user/'
.urlencode($this->getInput('u')).'/1';
break;
case 'By playlist id':
$uri.='playlist/'
.urlencode(strtok($this->getInput('p'), '_'));
break;
case 'From search results':
$uri.='search/'
.urlencode($this->getInput('s'));
if($this->getInput('pa')){
$uri.='/'.$this->getInput('pa');
}
break;
}
return $uri;
}
return $specific . ' : Dailymotion Bridge';
}
public function getURI(){
$uri = self::URI;
switch($this->queriedContext) {
case 'By username':
$uri .= 'user/' . urlencode($this->getInput('u')) . '/1';
break;
case 'By playlist id':
$uri .= 'playlist/' . urlencode(strtok($this->getInput('p'), '_'));
break;
case 'From search results':
$uri .= 'search/' . urlencode($this->getInput('s'));
if($this->getInput('pa')) {
$uri .= '/' . $this->getInput('pa');
}
break;
default: return parent::getURI();
}
return $uri;
}
}

View File

@@ -1,51 +1,136 @@
<?php
class DanbooruBridge extends BridgeAbstract{
class DanbooruBridge extends BridgeAbstract {
const MAINTAINER = "mitsukarenai";
const NAME = "Danbooru";
const URI = "http://donmai.us/";
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = "Returns images from given page";
const MAINTAINER = 'mitsukarenai, logmanoriginal';
const NAME = 'Danbooru';
const URI = 'http://donmai.us/';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = 'Returns images from given page';
const PARAMETERS = array(
'global'=>array(
'p'=>array(
'name'=>'page',
'defaultValue'=>1,
'type'=>'number'
),
't'=>array('name'=>'tags')
),
0=>array()
);
const PARAMETERS = array(
'global' => array(
'p' => array(
'name' => 'page',
'defaultValue' => 1,
'type' => 'number'
),
't' => array(
'name' => 'tags'
)
),
0 => array()
);
const PATHTODATA='article';
const IDATTRIBUTE='data-id';
const PATHTODATA = 'article';
const IDATTRIBUTE = 'data-id';
const TAGATTRIBUTE = 'alt';
protected function getFullURI(){
return $this->getURI().'posts?'
.'&page='.$this->getInput('p')
.'&tags='.urlencode($this->getInput('t'));
}
protected function getFullURI(){
return $this->getURI()
. 'posts?&page=' . $this->getInput('p')
. '&tags=' . urlencode($this->getInput('t'));
}
protected function getItemFromElement($element){
$item = array();
$item['uri'] = $this->getURI().$element->find('a', 0)->href;
$item['postid'] = (int)preg_replace("/[^0-9]/",'', $element->getAttribute(static::IDATTRIBUTE));
$item['timestamp'] = time();
$thumbnailUri = $this->getURI().$element->find('img', 0)->src;
$item['tags'] = $element->find('img', 0)->getAttribute('alt');
$item['title'] = $this->getName().' | '.$item['postid'];
$item['content'] = '<a href="' . $item['uri'] . '"><img src="' . $thumbnailUri . '" /></a><br>Tags: '.$item['tags'];
return $item;
}
protected function getTags($element){
return $element->find('img', 0)->getAttribute(static::TAGATTRIBUTE);
}
public function collectData(){
$html = getSimpleHTMLDOM($this->getFullURI())
or returnServerError('Could not request '.$this->getName());
protected function getItemFromElement($element){
// Fix links
defaultLinkTo($element, $this->getURI());
foreach($html->find(static::PATHTODATA) as $element) {
$this->items[] = $this->getItemFromElement($element);
}
}
$item = array();
$item['uri'] = $element->find('a', 0)->href;
$item['postid'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE));
$item['timestamp'] = time();
$thumbnailUri = $element->find('img', 0)->src;
$item['tags'] = $this->getTags($element);
$item['title'] = $this->getName() . ' | ' . $item['postid'];
$item['content'] = '<a href="'
. $item['uri']
. '"><img src="'
. $thumbnailUri
. '" /></a><br>Tags: '
. $item['tags'];
return $item;
}
public function collectData(){
$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

@@ -1,23 +1,28 @@
<?php
class DansTonChatBridge extends BridgeAbstract{
class DansTonChatBridge extends BridgeAbstract {
const MAINTAINER = "Astalaseven";
const NAME = "DansTonChat Bridge";
const URI = "http://danstonchat.com/";
const MAINTAINER = 'Astalaseven';
const NAME = 'DansTonChat Bridge';
const URI = 'https://danstonchat.com/';
const CACHE_TIMEOUT = 21600; //6h
const DESCRIPTION = "Returns latest quotes from DansTonChat.";
const DESCRIPTION = 'Returns latest quotes from DansTonChat.';
public function collectData(){
public function collectData(){
$html = getSimpleHTMLDOM(self::URI.'latest.html')
or returnServerError('Could not request DansTonChat.');
$html = getSimpleHTMLDOM(self::URI . 'latest.html')
or returnServerError('Could not request DansTonChat.');
foreach($html->find('div.item') as $element) {
$item = array();
$item['uri'] = $element->find('a', 0)->href;
$item['title'] = 'DansTonChat '.$element->find('a', 1)->plaintext;
$item['content'] = $element->find('a', 0)->innertext;
$this->items[] = $item;
}
}
foreach($html->find('div.item') as $element) {
$item = array();
$item['uri'] = $element->find('a', 0)->href;
$titleContent = $element->find('h3 a', 0);
if($titleContent) {
$item['title'] = 'DansTonChat ' . html_entity_decode($titleContent->plaintext, ENT_QUOTES);
} else {
$item['title'] = 'DansTonChat';
}
$item['content'] = $element->find('div.item-content a', 0)->innertext;
$this->items[] = $item;
}
}
}

View File

@@ -1,57 +1,57 @@
<?php
class DauphineLibereBridge extends FeedExpander {
const MAINTAINER = "qwertygc";
const NAME = "Dauphine Bridge";
const URI = "http://www.ledauphine.com/";
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = "Returns the newest articles.";
const MAINTAINER = 'qwertygc';
const NAME = 'Dauphine Bridge';
const URI = 'https://www.ledauphine.com/';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Returns the newest articles.';
const PARAMETERS = array( array(
'u'=>array(
'name'=>'Catégorie de l\'article',
'type'=>'list',
'values'=>array(
'À la une'=>'',
'France Monde'=>'france-monde',
'Faits Divers'=>'faits-divers',
'Économie et Finance'=>'economie-et-finance',
'Politique'=>'politique',
'Sport'=>'sport',
'Ain'=>'ain',
'Alpes-de-Haute-Provence'=>'haute-provence',
'Hautes-Alpes'=>'hautes-alpes',
'Ardèche'=>'ardeche',
'Drôme'=>'drome',
'Isère Sud'=>'isere-sud',
'Savoie'=>'savoie',
'Haute-Savoie'=>'haute-savoie',
'Vaucluse'=>'vaucluse'
)
)
));
const PARAMETERS = array( array(
'u' => array(
'name' => 'Catégorie de l\'article',
'type' => 'list',
'values' => array(
'À la une' => '',
'France Monde' => 'france-monde',
'Faits Divers' => 'faits-divers',
'Économie et Finance' => 'economie-et-finance',
'Politique' => 'politique',
'Sport' => 'sport',
'Ain' => 'ain',
'Alpes-de-Haute-Provence' => 'haute-provence',
'Hautes-Alpes' => 'hautes-alpes',
'Ardèche' => 'ardeche',
'Drôme' => 'drome',
'Isère Sud' => 'isere-sud',
'Savoie' => 'savoie',
'Haute-Savoie' => 'haute-savoie',
'Vaucluse' => 'vaucluse'
)
)
));
public function collectData(){
$url = self::URI . 'rss';
public function collectData(){
$url = self::URI . 'rss';
if (empty($this->getInput('u'))) {
$url = self::URI . $this->getInput('u') . '/rss';
}
if(empty($this->getInput('u'))) {
$url = self::URI . $this->getInput('u') . '/rss';
}
$this->collectExpandableDatas($url, 10);
}
$this->collectExpandableDatas($url, 10);
}
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
$item['content'] = $this->ExtractContent($item['uri']);
return $item;
}
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
$item['content'] = $this->extractContent($item['uri']);
return $item;
}
private function ExtractContent($url) {
$html2 = getSimpleHTMLDOMCached($url);
$text = $html2->find('div.column', 0)->innertext;
$text = preg_replace('@<script[^>]*?>.*?</script>@si', '', $text);
return $text;
}
private function extractContent($url){
$html2 = getSimpleHTMLDOMCached($url);
foreach ($html2->find('.noprint, link, script, iframe, .shareTool, .contentInfo') as $remove) {
$remove->outertext = '';
}
return $html2->find('div.content', 0)->innertext;
}
}
?>

1470
bridges/DealabsBridge.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +1,46 @@
<?php
class DemoBridge extends BridgeAbstract{
class DemoBridge extends BridgeAbstract {
const MAINTAINER = "teromene";
const NAME = "DemoBridge";
const URI = "http://github.com/rss-bridge/rss-bridge";
const DESCRIPTION = "Bridge used for demos";
const MAINTAINER = 'teromene';
const NAME = 'DemoBridge';
const URI = 'http://github.com/rss-bridge/rss-bridge';
const DESCRIPTION = 'Bridge used for demos';
const PARAMETERS = array(
'testCheckbox' => array(
'testCheckbox'=>array(
'type'=>'checkbox',
'name'=>'test des checkbox'
)
),
const PARAMETERS = array(
'testCheckbox' => array(
'testCheckbox' => array(
'type' => 'checkbox',
'name' => 'test des checkbox'
)
),
'testList' => array(
'testList' => array(
'type' => 'list',
'name' => 'test des listes',
'values' => array(
'Test' => 'test',
'Test 2' => 'test2'
)
)
),
'testNumber' => array(
'testNumber' => array(
'type' => 'number',
'name' => 'test des numéros',
'exampleValue' => '1515632'
)
)
);
'testList' => array(
'testList'=>array(
'type'=>'list',
'name'=>'test des listes',
'values'=>array(
'Test'=>'test',
'Test 2'=>'test2'
)
)
),
public function collectData(){
'testNumber' => array(
'testNumber'=>array(
'type'=>'number',
'name'=>'test des numéros',
'exampleValue'=>'1515632'
)
)
);
$item = array();
$item['author'] = 'Me!';
$item['title'] = 'Test';
$item['content'] = 'Awesome content !';
$item['id'] = 'Lalala';
$item['uri'] = 'http://example.com/test';
public function collectData(){
$item = array();
$item['author'] = "Me!";
$item['title'] = "Test";
$item['content'] = "Awesome content !";
$item['id'] = "Lalala";
$item['uri'] = "http://example.com/test";
$this->items[] = $item;
}
$this->items[] = $item;
}
}

169
bridges/DemonoidBridge.php Normal file
View File

@@ -0,0 +1,169 @@
<?php
class DemonoidBridge extends BridgeAbstract {
const MAINTAINER = 'metaMMA';
const NAME = 'Demonoid';
const URI = 'https://www.demonoid.pw/';
const DESCRIPTION = 'Returns results from search';
const PARAMETERS = array(
'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 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
)
)
),
'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() {
if(!empty($this->getInput('q'))) {
$html = getSimpleHTMLDOM(
self::URI .
'files/?category=' .
rawurlencode($this->getInput('category')) .
'&subcategory=All&quality=All&seeded=2&external=2&query=' .
urlencode($this->getInput('q')) .
'&uid=0&sort='
) or returnServerError('Could not request Demonoid.');
} elseif(!empty($this->getInput('catOnly'))) {
$html = getSimpleHTMLDOM(
self::URI .
'files/?uid=0&category=' .
rawurlencode($this->getInput('catOnly')) .
'&subcategory=0&language=0&seeded=2&quality=0&query=&sort='
) or returnServerError('Could not request Demonoid.');
} elseif(!empty($this->getInput('userid'))) {
$html = getSimpleHTMLDOM(
self::URI .
'files/?uid=' .
rawurlencode($this->getInput('userid')) .
'&seeded=2'
) or returnServerError('Could not request Demonoid.');
} else {
returnServerError('Invalid parameters !');
}
if(preg_match('~No torrents found~', $html)) {
return;
}
$table = $html->find('td[class=ctable_content_no_pad]', 0);
$cursorCount = 4;
$elementCount = 0;
while($elementCount != 40) {
$elementCount++;
$currentElement = $table->find('tr', $cursorCount);
if(preg_match('~items total~', $currentElement)) {
break;
}
$item = array();
//Do we have a date ?
if(preg_match('~Added.*?(.*)~', $currentElement->plaintext, $dateStr)) {
if(preg_match('~today~', $dateStr[0])) {
date_default_timezone_set('UTC');
$timestamp = mktime(0, 0, 0, gmdate('n'), gmdate('j'), gmdate('Y'));
} else {
preg_match('~(?<=ed on ).*\d+~', $currentElement->plaintext, $fullDateStr);
date_default_timezone_set('UTC');
$dateObj = strptime($fullDateStr[0], '%A, %b %d, %Y');
$timestamp = mktime(0, 0, 0, $dateObj['tm_mon'] + 1, $dateObj['tm_mday'], 1900 + $dateObj['tm_year']);
}
$cursorCount++;
}
$content = $table->find('tr', $cursorCount)->find('a', 1);
$cursorCount++;
$torrentInfo = $table->find('tr', $cursorCount);
$item['timestamp'] = $timestamp;
$item['title'] = $content->plaintext;
$item['id'] = self::URI . $content->href;
$item['uri'] = self::URI . $content->href;
$item['author'] = $torrentInfo->find('a[class=user]', 0)->plaintext;
$item['seeders'] = $torrentInfo->find('font[class=green]', 0)->plaintext;
$item['leechers'] = $torrentInfo->find('font[class=red]', 0)->plaintext;
$item['size'] = $torrentInfo->find('td', 3)->plaintext;
$item['content'] = 'Uploaded by ' . $item['author']
. ' , Size ' . $item['size']
. '<br>seeders: '
. $item['seeders']
. ' | leechers: '
. $item['leechers']
. '<br><a href="'
. $item['id']
. '">info page</a>';
$this->items[] = $item;
$cursorCount++;
}
}
}

View File

@@ -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

@@ -1,11 +1,11 @@
<?php
class DeveloppezDotComBridge extends FeedExpander {
const MAINTAINER = "polopollo";
const NAME = "Developpez.com Actus (FR)";
const URI = "http://www.developpez.com/";
const MAINTAINER = 'polopollo';
const NAME = 'Developpez.com Actus (FR)';
const URI = 'https://www.developpez.com/';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = "Returns the 15 newest posts from DeveloppezDotCom (full text).";
const DESCRIPTION = 'Returns the 15 newest posts from DeveloppezDotCom (full text).';
public function collectData(){
$this->collectExpandableDatas(self::URI . 'index/rss', 15);
@@ -13,19 +13,13 @@ class DeveloppezDotComBridge extends FeedExpander {
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
$item['content'] = $this->DeveloppezDotComExtractContent($item['uri']);
$item['content'] = $this->extractContent($item['uri']);
return $item;
}
private function DeveloppezDotComStripCDATA($string) {
$string = str_replace('<![CDATA[', '', $string);
$string = str_replace(']]>', '', $string);
return $string;
}
// F***ing quotes from Microsoft Word badly encoded, here was the trick:
// http://stackoverflow.com/questions/1262038/how-to-replace-microsoft-encoded-quotes-in-php
private function convert_smart_quotes($string)
private function convertSmartQuotes($string)
{
$search = array(chr(145),
chr(146),
@@ -33,18 +27,20 @@ class DeveloppezDotComBridge extends FeedExpander {
chr(148),
chr(151));
$replace = array("'",
"'",
'"',
'"',
'-');
$replace = array(
"'",
"'",
'"',
'"',
'-'
);
return str_replace($search, $replace, $string);
}
private function DeveloppezDotComExtractContent($url) {
private function extractContent($url){
$articleHTMLContent = getSimpleHTMLDOMCached($url);
$text = $this->convert_smart_quotes($articleHTMLContent->find('div.content', 0)->innertext);
$text = $this->convertSmartQuotes($articleHTMLContent->find('div.content', 0)->innertext);
$text = utf8_encode($text);
return trim($text);
}

124
bridges/DiceBridge.php Normal file
View File

@@ -0,0 +1,124 @@
<?php
class DiceBridge extends BridgeAbstract {
const MAINTAINER = 'rogerdc';
const NAME = 'Dice Unofficial RSS';
const URI = 'https://www.dice.com/';
const DESCRIPTION = 'The Unofficial Dice RSS';
// const CACHE_TIMEOUT = 86400; // 1 day
const PARAMETERS = array(array(
'for_one' => array(
'name' => 'With at least one of the words',
'required' => false,
),
'for_all' => array(
'name' => 'With all of the words',
'required' => false,
),
'for_exact' => array(
'name' => 'With the exact phrase',
'required' => false,
),
'for_none' => array(
'name' => 'With none of these words',
'required' => false,
),
'for_jt' => array(
'name' => 'Within job title',
'required' => false,
),
'for_com' => array(
'name' => 'Within company name',
'required' => false,
),
'for_loc' => array(
'name' => 'City, State, or ZIP code',
'required' => false,
),
'radius' => array(
'name' => 'Radius in miles',
'type' => 'list',
'required' => false,
'values' => array(
'Exact Location' => 'El',
'Within 5 miles' => '5',
'Within 10 miles' => '10',
'Within 20 miles' => '20',
'Within 30 miles' => '0',
'Within 40 miles' => '40',
'Within 50 miles' => '50',
'Within 75 miles' => '75',
'Within 100 miles' => '100',
),
'defaultValue' => '0',
),
'jtype' => array(
'name' => 'Job type',
'type' => 'list',
'required' => false,
'values' => array(
'Full-Time' => 'Full Time',
'Part-Time' => 'Part Time',
'Contract - Independent' => 'Contract Independent',
'Contract - W2' => 'Contract W2',
'Contract to Hire - Independent' => 'C2H Independent',
'Contract to Hire - W2' => 'C2H W2',
'Third Party - Contract - Corp-to-Corp' => 'Contract Corp-To-Corp',
'Third Party - Contract to Hire - Corp-to-Corp' => 'C2H Corp-To-Corp',
),
'defaultValue' => 'Full Time',
),
'telecommute' => array(
'name' => 'Telecommute',
'type' => 'checkbox',
),
));
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'));
$uri .= '&for_all=' . urlencode($this->getInput('for_all'));
$uri .= '&for_exact=' . urlencode($this->getInput('for_exact'));
$uri .= '&for_none=' . urlencode($this->getInput('for_none'));
$uri .= '&for_jt=' . urlencode($this->getInput('for_jt'));
$uri .= '&for_com=' . urlencode($this->getInput('for_com'));
$uri .= '&for_loc=' . urlencode($this->getInput('for_loc'));
if ($this->getInput('jtype')) {
$uri .= '&jtype=' . urlencode($this->getInput('jtype'));
}
$uri .= '&sort=date&limit=100';
$uri .= '&radius=' . urlencode($this->getInput('radius'));
if ($this->getInput('telecommute')) {
$uri .= '&telecommute=true';
}
$html = getSimpleHTMLDOM($uri)
or returnServerError('Could not request Dice.');
foreach($html->find('div.complete-serp-result-div') as $element) {
$item = array();
// Title
$masterLink = $element->find('a[id^=position]', 0);
$item['title'] = $masterLink->title;
// URL
$uri = $masterLink->href;
// $uri = substr($uri, 0, strrpos($uri, '?'));
$item['uri'] = substr($uri, 0, strrpos($uri, '?'));
// ID
$item['id'] = $masterLink->value;
// Image
$image = $element->find('img', 0);
if ($image)
$item['image'] = $image->getAttribute('src');
// Content
$shortdesc = $element->find('.shortdesc', '0');
$shortdesc = ($shortdesc) ? $shortdesc->innertext : '';
$item['content'] = $shortdesc;
$this->items[] = $item;
}
}
}

View File

@@ -1,36 +1,36 @@
<?php
class DilbertBridge extends BridgeAbstract {
const MAINTAINER = 'kranack';
const NAME = 'Dilbert Daily Strip';
const URI = 'http://dilbert.com';
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'The Unofficial Dilbert Daily Comic Strip';
const MAINTAINER = 'kranack';
const NAME = 'Dilbert Daily Strip';
const URI = 'https://dilbert.com';
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'The Unofficial Dilbert Daily Comic Strip';
public function collectData(){
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) {
foreach($html->find('section.comic-item') as $element) {
$img = $element->find('img', 0);
$link = $element->find('a', 0);
$comic = $img->src;
$title = $link->alt;
$url = $link->href;
$date = substr($url, 25);
if (empty($title))
$title = 'Dilbert Comic Strip on '.$date;
$date = strtotime($date);
$img = $element->find('img', 0);
$link = $element->find('a', 0);
$comic = $img->src;
$title = $img->alt;
$url = $link->href;
$date = substr(strrchr($url, '/'), 1);
if (empty($title))
$title = 'Dilbert Comic Strip on ' . $date;
$date = strtotime($date);
$item = array();
$item['uri'] = $url;
$item['title'] = $title;
$item['author'] = 'Scott Adams';
$item['timestamp'] = $date;
$item['content'] = '<img src="'.$comic.'" alt="'.$img->alt.'" />';
$this->items[] = $item;
}
}
$item = array();
$item['uri'] = $url;
$item['title'] = $title;
$item['author'] = 'Scott Adams';
$item['timestamp'] = $date;
$item['content'] = '<img src="' . $comic . '" alt="' . $img->alt . '" />';
$this->items[] = $item;
}
}
}
?>

116
bridges/DiscogsBridge.php Normal file
View File

@@ -0,0 +1,116 @@
<?php
class DiscogsBridge extends BridgeAbstract {
const MAINTAINER = 'teromene';
const NAME = 'DiscogsBridge';
const URI = 'https://www.discogs.com/';
const DESCRIPTION = 'Returns releases from discogs';
const PARAMETERS = array(
'Artist Releases' => array(
'artistid' => array(
'name' => 'Artist ID',
'type' => 'number',
)
),
'Label Releases' => array(
'labelid' => array(
'name' => 'Label ID',
'type' => 'number',
)
),
'User Wantlist' => array(
'username_wantlist' => array(
'name' => 'Username',
'type' => 'text',
)
),
'User Folder' => array(
'username_folder' => array(
'name' => 'Username',
'type' => 'text',
),
'folderid' => array(
'name' => 'Folder ID',
'type' => 'number',
)
)
);
public function collectData() {
if(!empty($this->getInput('artistid')) || !empty($this->getInput('labelid'))) {
if(!empty($this->getInput('artistid'))) {
$data = getContents('https://api.discogs.com/artists/'
. $this->getInput('artistid')
. '/releases?sort=year&sort_order=desc')
or returnServerError('Unable to query discogs !');
} elseif(!empty($this->getInput('labelid'))) {
$data = getContents('https://api.discogs.com/labels/'
. $this->getInput('labelid')
. '/releases?sort=year&sort_order=desc')
or returnServerError('Unable to query discogs !');
}
$jsonData = json_decode($data, true);
foreach($jsonData['releases'] as $release) {
$item = array();
$item['author'] = $release['artist'];
$item['title'] = $release['title'];
$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;
if(isset($release['year'])) {
$item['timestamp'] = DateTime::createFromFormat('Y', $release['year'])->getTimestamp();
}
$item['content'] = $item['author'] . ' - ' . $item['title'];
$this->items[] = $item;
}
} elseif(!empty($this->getInput('username_wantlist')) || !empty($this->getInput('username_folder'))) {
if(!empty($this->getInput('username_wantlist'))) {
$data = getContents('https://api.discogs.com/users/'
. $this->getInput('username_wantlist')
. '/wants?sort=added&sort_order=desc')
or returnServerError('Unable to query discogs !');
$jsonData = json_decode($data, true)['wants'];
} elseif(!empty($this->getInput('username_folder'))) {
$data = getContents('https://api.discogs.com/users/'
. $this->getInput('username_folder')
. '/collection/folders/'
. $this->getInput('folderid')
. '/releases?sort=added&sort_order=desc')
or returnServerError('Unable to query discogs !');
$jsonData = json_decode($data, true)['releases'];
}
foreach($jsonData as $element) {
$infos = $element['basic_information'];
$item = array();
$item['title'] = $infos['title'];
$item['author'] = $infos['artists'][0]['name'];
$item['id'] = $infos['artists'][0]['id'];
$item['uri'] = self::URI . $infos['artists'][0]['id'] . '/release/' . $infos['id'];
$item['timestamp'] = strtotime($element['date_added']);
$item['content'] = $item['author'] . ' - ' . $item['title'];
$this->items[] = $item;
}
}
}
public function getURI() {
return self::URI;
}
public function getName() {
return static::NAME;
}
}

View File

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

View File

@@ -0,0 +1,96 @@
<?php
class DribbbleBridge extends BridgeAbstract {
const MAINTAINER = 'quentinus95';
const NAME = 'Dribbble popular shots';
const URI = 'https://dribbble.com';
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');
$json = $this->loadEmbeddedJsonData($html);
foreach($html->find('li[id^="screenshot-"]') as $shot) {
$item = [];
$additional_data = $this->findJsonForShot($shot, $json);
if ($additional_data === null) {
$item['uri'] = self::URI . $shot->find('a', 0)->href;
$item['title'] = $shot->find('.dribbble-over strong', 0)->plaintext;
} else {
$item['timestamp'] = strtotime($additional_data['published_at']);
$item['uri'] = self::URI . $additional_data['path'];
$item['title'] = $additional_data['title'];
}
$item['author'] = trim($shot->find('.attribution-user a', 0)->plaintext);
$description = $shot->find('.comment', 0);
$item['content'] = $description === null ? '' : $description->plaintext;
$preview_path = $shot->find('picture source', 0)->attr['srcset'];
$item['content'] .= $this->getImageTag($preview_path, $item['title']);
$item['enclosures'] = [$this->getFullSizeImagePath($preview_path)];
$this->items[] = $item;
}
}
private function loadEmbeddedJsonData($html){
$json = [];
$scripts = $html->find('script');
foreach($scripts as $script) {
if(strpos($script->innertext, 'newestShots') !== false) {
// fix single quotes
$script->innertext = str_replace('\'', '"', $script->innertext);
// fix JavaScript JSON (why do they not adhere to the standard?)
$script->innertext = preg_replace('/(\w+):/i', '"\1":', $script->innertext);
// find beginning of JSON array
$start = strpos($script->innertext, '[');
// find end of JSON array, compensate for missing character!
$end = strpos($script->innertext, '];') + 1;
// convert JSON to PHP array
$json = json_decode(substr($script->innertext, $start, $end - $start), true);
break;
}
}
return $json;
}
private function findJsonForShot($shot, $json){
foreach($json as $element) {
if(strpos($shot->getAttribute('id'), (string)$element['id']) !== false) {
return $element;
}
}
return null;
}
private function getImageTag($preview_path, $title){
return sprintf(
'<br /> <a href="%s"><img src="%s" alt="%s" /></a>',
$this->getFullSizeImagePath($preview_path),
$preview_path,
$title
);
}
private function getFullSizeImagePath($preview_path){
return str_replace('_1x', '', $preview_path);
}
}

View File

@@ -1,41 +1,42 @@
<?php
class DuckDuckGoBridge extends BridgeAbstract{
class DuckDuckGoBridge extends BridgeAbstract {
const MAINTAINER = "Astalaseven";
const NAME = "DuckDuckGo";
const URI = "https://duckduckgo.com/";
const MAINTAINER = 'Astalaseven';
const NAME = 'DuckDuckGo';
const URI = 'https://duckduckgo.com/';
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = "Returns results from DuckDuckGo.";
const DESCRIPTION = 'Returns results from DuckDuckGo.';
const SORT_DATE = '+sort:date';
const SORT_RELEVANCE = '';
const PARAMETERS = array( array(
'u'=>array(
'name'=>'keyword',
'required'=>true),
'sort'=>array(
'name'=>'sort by',
'type'=>'list',
'required'=>false,
'values'=>array(
'date'=>self::SORT_DATE,
'relevance'=>self::SORT_RELEVANCE
),
'defaultValue'=>self::SORT_DATE
)
));
const PARAMETERS = array( array(
'u' => array(
'name' => 'keyword',
'required' => true
),
'sort' => array(
'name' => 'sort by',
'type' => 'list',
'required' => false,
'values' => array(
'date' => self::SORT_DATE,
'relevance' => self::SORT_RELEVANCE
),
'defaultValue' => self::SORT_DATE
)
));
public function collectData(){
$html = getSimpleHTMLDOM(self::URI.'html/?q='.$this->getInput('u').$this->getInput('sort'))
or returnServerError('Could not request DuckDuckGo.');
public function collectData(){
$html = getSimpleHTMLDOM(self::URI . 'html/?kd=-1&q=' . $this->getInput('u') . $this->getInput('sort'))
or returnServerError('Could not request DuckDuckGo.');
foreach($html->find('div.results_links') as $element) {
$item = array();
$item['uri'] = $element->find('a', 0)->href;
$item['title'] = $element->find('a', 1)->innertext;
$item['content'] = $element->find('div.snippet', 0)->plaintext;
$this->items[] = $item;
}
}
foreach($html->find('div.results_links') as $element) {
$item = array();
$item['uri'] = $element->find('a', 0)->href;
$item['title'] = $element->find('a', 1)->innertext;
$item['content'] = $element->find('div.snippet', 0)->plaintext;
$this->items[] = $item;
}
}
}

161
bridges/ETTVBridge.php Normal file
View File

@@ -0,0 +1,161 @@
<?php
class ETTVBridge extends BridgeAbstract {
const MAINTAINER = 'GregThib';
const NAME = 'ETTV';
const URI = 'https://www.ettv.tv/';
const DESCRIPTION = 'Returns list of 20 latest torrents for a specific search.';
const CACHE_TIMEOUT = 14400; // 4 hours
const PARAMETERS = array( array(
'query' => array(
'name' => 'Keywords',
'required' => true
),
'cat' => array(
'type' => 'list',
'name' => 'Category',
'values' => array(
'(ALL TYPES)' => '0',
'Anime: Movies' => '73',
'Anime: Dubbed/Subbed' => '74',
'Anime: Others' => '75',
'Books: Ebooks' => '53',
'Books: Magazines' => '54',
'Books: Comics' => '55',
'Books: Audio' => '56',
'Books: Others' => '68',
'Games: Windows' => '57',
'Games: Android' => '58',
'Games: Others' => '71',
'Movies: HD 1080p' => '1',
'Movies: HD 720p' => '2',
'Movies: UltraHD/4K' => '3',
'Movies: XviD' => '42',
'Movies: X264/H264' => '47',
'Movies: 3D' => '49',
'Movies: Dubs/Dual Audio' => '51',
'Movies: CAM/TS' => '65',
'Movies: BluRay Disc/Remux' => '66',
'Movies: DVDR' => '67',
'Movies: HEVC/x265' => '76',
'Music: MP3' => '59',
'Music: FLAC' => '60',
'Music: Music Videos' => '61',
'Music: Others' => '69',
'Software: Windows' => '62',
'Software: Android' => '63',
'Software: Mac' => '64',
'Software: Others' => '70',
'TV: HD/X264/H264' => '41',
'TV: SD/X264/H264' => '5',
'TV: TV Packs' => '7',
'TV: SD/XVID' => '50',
'TV: Sport' => '72',
'TV: HEVC/x265' => '77',
'Unsorted: Unsorted' => '78'
),
'defaultValue' => '(ALL TYPES)'
),
'status' => array(
'type' => 'list',
'name' => 'Status',
'values' => array(
'Active Transfers' => '0',
'Included Dead' => '1',
'Only Dead' => '2'
),
'defaultValue' => 'Included Dead'
),
'lang' => array(
'type' => 'list',
'name' => 'Lang',
'values' => array(
'(ALL)' => '0',
'Arabic' => '17',
'Chinese ' => '10',
'Danish' => '13',
'Dutch' => '11',
'English' => '1',
'Finnish' => '18',
'French' => '2',
'German' => '3',
'Greek' => '15',
'Hindi' => '8',
'Italian' => '4',
'Japanese' => '5',
'Korean' => '9',
'Polish' => '14',
'Russian' => '7',
'Spanish' => '6',
'Turkish' => '16'
),
'defaultValue' => '(ALL)'
)
));
protected $results_link;
public function collectData(){
// No control on inputs, because all defaultValue are set
$query_str = 'torrents-search.php';
$query_str .= '?search=' . urlencode('+' . str_replace(' ', ' +', $this->getInput('query')));
$query_str .= '&cat=' . $this->getInput('cat');
$query_str .= '&incldead=' . $this->getInput('status');
$query_str .= '&lang=' . $this->getInput('lang');
$query_str .= '&sort=id&order=desc';
// Get results page
$this->results_link = self::URI . $query_str;
$html = getSimpleHTMLDOM($this->results_link)
or returnServerError('Could not request ' . $this->getName());
// Loop on each entry
foreach($html->find('table.table tr') as $element) {
if($element->parent->tag == 'thead') continue;
$entry = $element->find('td', 1)->find('a', 0);
// retrieve result page to get more details
$link = rtrim(self::URI, '/') . $entry->href;
$page = getSimpleHTMLDOM($link)
or returnServerError('Could not request page ' . $link);
// get details & download links
$details = $page->find('fieldset.download table', 0); // WHAT?? It should be the second one…
$dllinks = $page->find('div#downloadbox table', 0);
// fill item
$item = array();
$item['author'] = $details->children(6)->children(1)->plaintext;
$item['title'] = $entry->title;
$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;
$item['content'] .= '<br/><b>Lang: </b>' . $details->children(3)->children(1)->innertext;
$item['content'] .= '<br/><b>Size: </b>' . $details->children(4)->children(1)->innertext;
$item['content'] .= '<br/><b>Hash: </b>' . $details->children(5)->children(1)->innertext;
foreach($dllinks->children(0)->children(1)->find('a') as $dl) {
$item['content'] .= '<br/>' . $dl->outertext;
}
$item['content'] .= '<br/><br/>' . $details->children(1)->children(0)->innertext;
$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

@@ -1,66 +1,67 @@
<?php
class EZTVBridge extends BridgeAbstract{
class EZTVBridge extends BridgeAbstract {
const MAINTAINER = "alexAubin";
const NAME = "EZTV";
const URI = "https://eztv.ch/";
const DESCRIPTION = "Returns list of *recent* torrents for a specific show on EZTV. Get showID from URLs in https://eztv.ch/shows/showID/show-full-name.";
const MAINTAINER = 'alexAubin';
const NAME = 'EZTV';
const URI = 'https://eztv.ch/';
const DESCRIPTION = 'Returns list of *recent* torrents for a specific show
on EZTV. Get showID from URLs in https://eztv.ch/shows/showID/show-full-name.';
const PARAMETERS = array( array(
'i'=>array(
'name'=>'Show ids',
'exampleValue'=>'showID1,showID2,…',
'required'=>true
)
));
const PARAMETERS = array( array(
'i' => array(
'name' => 'Show ids',
'exampleValue' => 'showID1,showID2,…',
'required' => true
)
));
public function collectData(){
// Make timestamp from relative released time in table
function makeTimestamp($relativeReleaseTime){
// Make timestamp from relative released time in table
function makeTimestamp($relativeReleaseTime){
$relativeDays = 0;
$relativeHours = 0;
$relativeDays = 0;
$relativeHours = 0;
foreach (explode(" ",$relativeReleaseTime) as $relativeTimeElement) {
if (substr($relativeTimeElement,-1) == "d") $relativeDays = substr($relativeTimeElement,0,-1);
if (substr($relativeTimeElement,-1) == "h") $relativeHours = substr($relativeTimeElement,0,-1);
}
return mktime(date('h')-$relativeHours,0,0,date('m'),date('d')-$relativeDays,date('Y'));
}
foreach(explode(' ', $relativeReleaseTime) as $relativeTimeElement) {
if(substr($relativeTimeElement, -1) == 'd') $relativeDays = substr($relativeTimeElement, 0, -1);
if(substr($relativeTimeElement, -1) == 'h') $relativeHours = substr($relativeTimeElement, 0, -1);
}
return mktime(date('h') - $relativeHours, 0, 0, date('m'), date('d') - $relativeDays, date('Y'));
}
// Loop on show ids
$showList = explode(",",$this->getInput('i'));
foreach($showList as $showID){
// Loop on show ids
$showList = explode(',', $this->getInput('i'));
foreach($showList as $showID) {
// Get show page
$html = getSimpleHTMLDOM(self::URI.'shows/'.rawurlencode($showID).'/')
or returnServerError('Could not request EZTV for id "'.$showID.'"');
// Get show page
$html = getSimpleHTMLDOM(self::URI . 'shows/' . rawurlencode($showID) . '/')
or returnServerError('Could not request EZTV for id "' . $showID . '"');
// Loop on each element that look like an episode entry...
foreach($html->find('.forum_header_border') as $element) {
// Loop on each element that look like an episode entry...
foreach($html->find('.forum_header_border') as $element) {
// Filter entries that are not episode entries
$ep = $element->find('td',1);
if (empty($ep)) continue;
$epinfo = $ep->find('.epinfo',0);
$released = $element->find('td',3);
if (empty($epinfo)) continue;
if (empty($released->plaintext)) continue;
// Filter entries that are not episode entries
$ep = $element->find('td', 1);
if(empty($ep)) continue;
$epinfo = $ep->find('.epinfo', 0);
$released = $element->find('td', 3);
if(empty($epinfo)) continue;
if(empty($released->plaintext)) continue;
// Filter entries that are older than 1 week
if ($released->plaintext == '&gt;1 week') continue;
// Filter entries that are older than 1 week
if($released->plaintext == '&gt;1 week') continue;
// Fill item
$item = array();
$item['uri'] = self::URI.$epinfo->href;
$item['id'] = $item['uri'];
$item['timestamp'] = makeTimestamp($released->plaintext);
$item['title'] = $epinfo->plaintext;
$item['content'] = $epinfo->alt;
if(isset($item['title']))
$this->items[] = $item;
}
}
// Fill item
$item = array();
$item['uri'] = self::URI . $epinfo->href;
$item['id'] = $item['uri'];
$item['timestamp'] = makeTimestamp($released->plaintext);
$item['title'] = $epinfo->plaintext;
$item['content'] = $epinfo->alt;
if(isset($item['title']))
$this->items[] = $item;
}
}
}
}

View File

@@ -1,25 +1,41 @@
<?php
class EliteDangerousGalnetBridge extends BridgeAbstract
{
const MAINTAINER = "corenting";
const NAME = "Elite: Dangerous Galnet";
const URI = "https://community.elitedangerous.com/galnet/";
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = "Returns the latest page of news from Galnet";
class EliteDangerousGalnetBridge extends BridgeAbstract {
const MAINTAINER = 'corenting';
const NAME = 'Elite: Dangerous Galnet';
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(){
$language = $this->getInput('language');
$url = 'https://community.elitedangerous.com/';
$url = $url . $language . '/galnet';
$html = getSimpleHTMLDOM($url)
or returnServerError('Error while downloading the website content');
public function collectData()
{
$html = getSimpleHTMLDOM(self::URI)
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;

146
bridges/ElloBridge.php Normal file
View File

@@ -0,0 +1,146 @@
<?php
class ElloBridge extends BridgeAbstract {
const MAINTAINER = 'teromene';
const NAME = 'Ello Bridge';
const URI = 'https://ello.co/';
const CACHE_TIMEOUT = 4800; //2hours
const DESCRIPTION = 'Returns the newest posts for Ello';
const PARAMETERS = array(
'By User' => array(
'u' => array(
'name' => 'Username',
'required' => true,
'title' => 'Username'
)
),
'Search' => array(
's' => array(
'name' => 'Search',
'required' => true,
'title' => 'Search'
)
)
);
public function collectData() {
$header = array(
'Authorization: Bearer ' . $this->getAPIKey()
);
if(!empty($this->getInput('u'))) {
$postData = getContents(self::URI . 'api/v2/users/~' . urlencode($this->getInput('u')) . '/posts', $header) or
returnServerError('Unable to query Ello API.');
} else {
$postData = getContents(self::URI . 'api/v2/posts?terms=' . urlencode($this->getInput('s')), $header) or
returnServerError('Unable to query Ello API.');
}
$postData = json_decode($postData);
$count = 0;
foreach($postData->posts as $post) {
$item = array();
$item['author'] = $this->getUsername($post, $postData);
$item['timestamp'] = strtotime($post->created_at);
$item['title'] = strip_tags($this->findText($post->summary));
$item['content'] = $this->getPostContent($post->body);
$item['enclosures'] = $this->getEnclosures($post, $postData);
$item['uri'] = self::URI . $item['author'] . '/post/' . $post->token;
$content = $post->body;
$this->items[] = $item;
$count += 1;
}
}
private function findText($path) {
foreach($path as $summaryElement) {
if($summaryElement->kind == 'text') {
return $summaryElement->data;
}
}
return '';
}
private function getPostContent($path) {
$content = '';
foreach($path as $summaryElement) {
if($summaryElement->kind == 'text') {
$content .= $summaryElement->data;
} elseif ($summaryElement->kind == 'image') {
$alt = '';
if(property_exists($summaryElement->data, 'alt')) {
$alt = $summaryElement->data->alt;
}
$content .= '<img src="' . $summaryElement->data->url . '" alt="' . $alt . '" />';
}
}
return $content;
}
private function getEnclosures($post, $postData) {
$assets = [];
foreach($post->links->assets as $asset) {
foreach($postData->linked->assets as $assetLink) {
if($asset == $assetLink->id) {
$assets[] = $assetLink->attachment->original->url;
break;
}
}
}
return $assets;
}
private function getUsername($post, $postData) {
foreach($postData->linked->users as $user) {
if($user->id == $post->links->author->id) {
return $user->username;
}
}
}
private function getAPIKey() {
$cache = Cache::create('FileCache');
$cache->setPath(PATH_CACHE);
$cache->setParameters(['key']);
$key = $cache->loadData();
if($key == null) {
$keyInfo = getContents(self::URI . 'api/webapp-token') or
returnServerError('Unable to get token.');
$key = json_decode($keyInfo)->token->access_token;
$cache->saveData($key);
}
return $key;
}
public function getName(){
if(!is_null($this->getInput('u'))) {
return $this->getInput('u') . ' - Ello Bridge';
}
return parent::getName();
}
}

View File

@@ -1,22 +1,23 @@
<?php
class ElsevierBridge extends BridgeAbstract{
class ElsevierBridge extends BridgeAbstract {
const MAINTAINER = 'Pierre Mazière';
const NAME = 'Elsevier journals recent articles';
const URI = 'http://www.journals.elsevier.com/';
const CACHE_TIMEOUT = 43200; //12h
const DESCRIPTION = 'Returns the recent articles published in Elsevier journals';
const PARAMETERS = array( array(
'j'=>array(
'name'=>'Journal name',
'required'=>true,
'exampleValue'=>'academic-pediactrics',
'title'=>'Insert html-part of your journal'
)
));
const PARAMETERS = array( array(
'j' => array(
'name' => 'Journal name',
'required' => true,
'exampleValue' => 'academic-pediactrics',
'title' => 'Insert html-part of your journal'
)
));
// Extracts the list of names from an article as string
private function ExtractArticleName ($article){
private function extractArticleName($article){
$names = $article->find('small', 0);
if($names)
return trim($names->plaintext);
@@ -24,9 +25,9 @@ class ElsevierBridge extends BridgeAbstract{
}
// Extracts the timestamp from an article
private function ExtractArticleTimestamp ($article){
private function extractArticleTimestamp($article){
$time = $article->find('.article-info', 0);
if($time){
if($time) {
$timestring = trim($time->plaintext);
/*
The format depends on the age of an article:
@@ -34,11 +35,11 @@ class ElsevierBridge extends BridgeAbstract{
- July 2016
- MayJune 2016
*/
if(preg_match('/\S*(\d+\s\S+\s\d{4})/ims', $timestring, $matches)){
if(preg_match('/\S*(\d+\s\S+\s\d{4})/ims', $timestring, $matches)) {
return strtotime($matches[0]);
} elseif (preg_match('/[A-Za-z]+\-([A-Za-z]+\s\d{4})/ims', $timestring, $matches)){
} elseif (preg_match('/[A-Za-z]+\-([A-Za-z]+\s\d{4})/ims', $timestring, $matches)) {
return strtotime($matches[0]);
} elseif (preg_match('/([A-Za-z]+\s\d{4})/ims', $timestring, $matches)){
} elseif (preg_match('/([A-Za-z]+\s\d{4})/ims', $timestring, $matches)) {
return strtotime($matches[0]);
} else {
return 0;
@@ -48,27 +49,31 @@ class ElsevierBridge extends BridgeAbstract{
}
// Extracts the content from an article
private function ExtractArticleContent ($article){
private function extractArticleContent($article){
$content = $article->find('.article-content', 0);
if($content){
if($content) {
return trim($content->plaintext);
}
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) or returnServerError('No results for Elsevier journal '.$this->getInput('j'));
$html = getSimpleHTMLDOM($uri)
or returnServerError('No results for Elsevier journal ' . $this->getInput('j'));
foreach($html->find('.pod-listing') as $article){
foreach($html->find('.pod-listing') as $article) {
$item = array();
$item['uri'] = $article->find('.pod-listing-header>a',0)->getAttribute('href').'?np=y';
$item['title'] = $article->find('.pod-listing-header>a',0)->plaintext;
$item['author'] = $this->ExtractArticleName($article);
$item['timestamp'] = $this->ExtractArticleTimestamp($article);
$item['content'] = $this->ExtractArticleContent($article);
$item['uri'] = $article->find('.pod-listing-header>a', 0)->getAttribute('href') . '?np=y';
$item['title'] = $article->find('.pod-listing-header>a', 0)->plaintext;
$item['author'] = $this->extractArticleName($article);
$item['timestamp'] = $this->extractArticleTimestamp($article);
$item['content'] = $this->extractArticleContent($article);
$this->items[] = $item;
}
}
}
?>

View File

@@ -1,30 +1,27 @@
<?php
class EstCeQuonMetEnProdBridge extends BridgeAbstract {
const MAINTAINER = 'ORelio';
const NAME = 'Est-ce qu\'on met en prod aujourd\'hui ?';
const URI = 'https://www.estcequonmetenprodaujourdhui.info/';
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'Should we put a website in production today? (French)';
const MAINTAINER = 'ORelio';
const NAME = 'Est-ce qu\'on met en prod aujourd\'hui ?';
const URI = 'https://www.estcequonmetenprodaujourdhui.info/';
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;
}
public function collectData() {
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request EstCeQuonMetEnProd: ' . self::URI);
$html = getSimpleHTMLDOM($this->getURI()) or returnServerError('Could not request EstCeQuonMetEnProd: '.$this->getURI());
$item = array();
$item['uri'] = $this->getURI() . '#' . date('Y-m-d');
$item['title'] = $this->getName();
$item['author'] = 'Nicolas Hoffmann';
$item['timestamp'] = strtotime('today midnight');
$item['content'] = str_replace(
'src="/',
'src="' . self::URI,
trim(extractFromDelimiters($html->outertext, '<body role="document">', '<div id="share'))
);
$item = array();
$item['uri'] = $this->getURI().'#'.date('Y-m-d');
$item['title'] = $this->getName();
$item['author'] = 'Nicolas Hoffmann';
$item['timestamp'] = strtotime('today midnight');
$item['content'] = str_replace('src="/', 'src="'.$this->getURI(), trim(ExtractFromDelimiters($html->outertext, '<body role="document">', '<br /><br />')));
$this->items[] = $item;
}
$this->items[] = $item;
}
}
?>

84
bridges/EtsyBridge.php Normal file
View File

@@ -0,0 +1,84 @@
<?php
class EtsyBridge extends BridgeAbstract {
const NAME = 'Etsy search';
const URI = 'https://www.etsy.com';
const DESCRIPTION = 'Returns feeds for search results';
const MAINTAINER = 'logmanoriginal';
const PARAMETERS = array(
array(
'query' => array(
'name' => 'Search query',
'type' => 'text',
'required' => true,
'title' => 'Insert your search term here',
'exampleValue' => 'Enter your search term'
),
'queryextension' => array(
'name' => 'Query extension',
'type' => 'text',
'required' => false,
'title' => 'Insert additional query parts here
(anything after ?search=<your search query>)',
'exampleValue' => '&explicit=1&locationQuery=2921044'
),
'showimage' => array(
'name' => 'Show image in content',
'type' => 'checkbox',
'required' => false,
'title' => 'Activate to show the image in the content',
'defaultValue' => 'checked'
)
)
);
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Failed to receive ' . $this->getURI());
$results = $html->find('li.block-grid-item');
foreach($results as $result) {
// Skip banner cards (ads for categories)
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('p.text-gray-lighter', 0)->plaintext;
$item['content'] = '<p>'
. $result->find('span.currency-value', 0)->plaintext . ' '
. $result->find('span.currency-symbol', 0)->plaintext
. '</p><p>'
. $result->find('a', 0)->title
. '</p>';
$image = $result->find('img.display-block', 0)->src;
if($this->getInput('showimage')) {
$item['content'] .= '<img src="' . $image . '">';
}
$item['enclosures'] = array($image);
$this->items[] = $item;
}
}
public function getURI(){
if(!is_null($this->getInput('query'))) {
$uri = self::URI . '/search?q=' . urlencode($this->getInput('query'));
if(!is_null($this->getInput('queryextension'))) {
$uri .= $this->getInput('queryextension');
}
return $uri;
}
return parent::getURI();
}
}

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

290
bridges/FB2Bridge.php Normal file
View File

@@ -0,0 +1,290 @@
<?php
class FB2Bridge extends BridgeAbstract {
const MAINTAINER = 'teromene';
const NAME = 'Facebook Alternate';
const URI = 'https://www.facebook.com/';
const CACHE_TIMEOUT = 1000;
const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
please insert the parameter as follow : myExamplePage/132621766841117';
const PARAMETERS = array( array(
'u' => array(
'name' => 'Username',
'required' => true
)
));
public function getIcon() {
return 'https://static.xx.fbcdn.net/rsrc.php/yo/r/iRmz9lCMBD2.ico';
}
public function collectData(){
//Utility function for cleaning a Facebook link
$unescape_fb_link = function($matches){
if(is_array($matches) && count($matches) > 1) {
$link = $matches[1];
if(strpos($link, '/') === 0)
$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 . '"';
}
};
//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];
};
if($this->getInput('u') !== null) {
$page = 'https://touch.facebook.com/' . $this->getInput('u');
$cookies = $this->getCookies($page);
$pageInfo = $this->getPageInfos($page, $cookies);
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($pageInfo['userId'] == -1) {
echo <<<EOD
This page is not accessible without being logged in.
EOD;
die();
}
}
//Build the string for the first request
$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);
$html = $this->buildContent($fileContent);
$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'] = 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 = '';
}
if($content->find('footer', 0) !== null) {
$content->find('footer', 0)->innertext = '';
}
// Replace emoticon images by their textual representation (part of the span)
foreach($content->find('span[title*="emoticon"]') as $emoticon) {
$emoticon->innertext = $emoticon->find('span[aria-hidden="true"]', 0)->innertext;
}
//Remove html nodes, keep only img, links, basic formatting
$content = strip_tags($content, '<a><img><i><u><br><p><h3><h4><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);
//Clean useless html tag properties and fix link closing tags
foreach (array(
'onmouseover',
'onclick',
'target',
'ajaxify',
'tabindex',
'class',
'data-[^=]*',
'aria-[^=]*',
'role',
'rel',
'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);
//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)
$title = substr($title, 0, strpos(wordwrap($title, 24), "\n")) . '...';
$title = $title . ' | ' . strip_tags($content);
if (strlen($title) > 64)
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
$item['title'] = html_entity_decode($title, ENT_QUOTES);
$item['author'] = html_entity_decode($author, ENT_QUOTES);
$item['timestamp'] = html_entity_decode($timestamp, ENT_QUOTES);
if($item['timestamp'] != 0)
array_push($this->items, $item);
}
}
//Builds the HTML from the encoded JS that Facebook provides.
private function buildContent($pageContent){
// The html ends with:
// /div>","replaceifexists
$regex = '/\\"html\\":(\".+\/div>"),"replace/';
preg_match($regex, $pageContent, $result);
$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.
private function getCookies($pageURL){
$ctx = stream_context_create(array(
'http' => array(
'user_agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0',
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
)
)
);
$a = file_get_contents($pageURL, 0, $ctx);
//First request to get the cookie
$cookies = '';
foreach($http_response_header as $hdr) {
if(strpos($hdr, 'Set-Cookie') !== false) {
$cLine = explode(':', $hdr)[1];
$cLine = explode(';', $cLine)[0];
$cookies .= ';' . $cLine;
}
}
return substr($cookies, 1);
}
//Get the page ID and username from the Facebook page.
private function getPageInfos($page, $cookies){
$context = stream_context_create(array(
'http' => array(
'user_agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0',
'header' => 'Cookie: ' . $cookies
)
)
);
$pageContent = file_get_contents($page, 0, $context);
if(strpos($pageContent, 'signup-button') != false) {
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 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 array('userId' => $matches[1], 'username' => $username);
}
public function getName(){
return (isset($this->name) ? $this->name . ' - ' : '') . 'Facebook Bridge';
}
public function getURI(){
return 'http://facebook.com';
}
}

58
bridges/FDroidBridge.php Normal file
View File

@@ -0,0 +1,58 @@
<?php
class FDroidBridge extends BridgeAbstract {
const MAINTAINER = 'Mitsukarenai';
const NAME = 'F-Droid Bridge';
const URI = 'https://f-droid.org/';
const CACHE_TIMEOUT = 60 * 60 * 2; // 2 hours
const DESCRIPTION = 'Returns latest added/updated apps on the open-source Android apps repository F-Droid';
const PARAMETERS = array( array(
'u' => array(
'name' => 'Widget selection',
'type' => 'list',
'required' => true,
'values' => array(
'Latest added apps' => 'added',
'Latest updated apps' => 'updated'
)
)
));
public function getIcon() {
return self::URI . 'assets/favicon.ico?v=8j6PKzW9Mk';
}
public function collectData(){
$url = self::URI;
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request F-Droid.');
// targetting the corresponding widget based on user selection
// "updated" is the 4th widget on the page, "added" is the 5th
switch($this->getInput('u')) {
case 'updated':
$html_widget = $html->find('div.sidebar-widget', 4);
break;
default:
$html_widget = $html->find('div.sidebar-widget', 5);
break;
}
// and now extracting app info from the selected widget (and yeah turns out icons are of heterogeneous sizes)
foreach($html_widget->find('a') as $element) {
$item = array();
$item['uri'] = self::URI . $element->href;
$item['title'] = $element->find('h4', 0)->plaintext;
$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'];
$this->items[] = $item;
}
}
}

View File

@@ -1,210 +1,691 @@
<?php
class FacebookBridge extends BridgeAbstract{
class FacebookBridge extends BridgeAbstract {
const MAINTAINER = "teromene";
const NAME = "Facebook";
const URI = "https://www.facebook.com/";
const MAINTAINER = 'teromene, logmanoriginal';
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, please insert the parameter as follow : myExamplePage/132621766841117";
const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
please insert the parameter as follow : myExamplePage/132621766841117';
const PARAMETERS =array( array(
'u'=>array(
'name'=>'Username',
'required'=>true
)
));
const PARAMETERS = array(
'User' => array(
'u' => array(
'name' => 'Username',
'required' => true
),
'media_type' => array(
'name' => 'Media type',
'type' => 'list',
'required' => false,
'values' => array(
'All' => 'all',
'Video' => 'video',
'No Video' => 'novideo'
),
'defaultValue' => 'all'
),
'skip_reviews' => array(
'name' => 'Skip reviews',
'type' => 'checkbox',
'required' => false,
'defaultValue' => false,
'title' => 'Feed includes reviews when checked'
)
),
'Group' => array(
'g' => array(
'name' => 'Group',
'type' => 'text',
'required' => true,
'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 $authorName = '';
private $groupName = '';
public function collectData(){
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;
//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;
} 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.'"';
if (strpos($link, 'facebook.com/l.php?u=') !== false)
$link = urldecode(ExtractFromDelimiters($link, 'facebook.com/l.php?u=', '&'));
return ' href="'.$link.'"';
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';
}
public function collectData() {
switch($this->queriedContext) {
case 'Group':
$this->collectGroupData();
break;
case 'User':
$this->collectUserData();
break;
default:
returnClientError('Unknown context: "' . $this->queriedContext . '"!');
}
$limit = $this->getInput('limit') ?: -1;
if($limit > 0 && count($this->items) > $limit) {
$this->items = array_slice($this->items, 0, $limit);
}
}
#region Group
private function collectGroupData() {
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n");
$html = getSimpleHTMLDOM($this->getURI(), $header)
or returnServerError('Failed loading facebook page: ' . $this->getURI());
if(!$this->isPublicGroup($html)) {
returnClientError('This group is not public! RSS-Bridge only supports public groups!');
}
defaultLinkTo($html, substr(self::URI, 0, strlen(self::URI) - 1));
$this->groupName = $this->extractGroupName($html);
$posts = $html->find('div.userContentWrapper')
or returnServerError('Failed finding posts!');
foreach($posts as $post) {
$item = array();
$item['uri'] = $this->extractGroupURI($post);
$item['title'] = $this->extractGroupTitle($post);
$item['author'] = $this->extractGroupAuthor($post);
$item['content'] = $this->extractGroupContent($post);
$item['timestamp'] = $this->extractGroupTimestamp($post);
$item['enclosures'] = $this->extractGroupEnclosures($post);
$this->items[] = $item;
}
}
private function sanitizeGroup($group) {
if(filter_var(
$group,
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
// User provided a URL
$urlparts = parse_url($group);
if($urlparts['host'] !== parse_url(self::URI)['host']
&& 'www.' . $urlparts['host'] !== parse_url(self::URI)['host']) {
returnClientError('The host you provided is invalid! Received "'
. $urlparts['host']
. '", expected "'
. parse_url(self::URI)['host']
. '"!');
}
};
//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];
};
return explode('/', $urlparts['path'])[2];
$html = null;
} elseif(strpos($group, '/') !== false) {
returnClientError('The group you provided is invalid: ' . $group);
} else {
return $group;
}
//Handle captcha response sent by the viewer
if (isset($_POST['captcha_response']))
{
}
private function isPublicGroup($html) {
// Facebook redirects to the groups about page for non-public groups
$about = $html->find('#pagelet_group_about', 0);
return !($about);
}
private function extractGroupName($html) {
$ogtitle = $html->find('meta[property="og:title"]', 0)
or returnServerError('Unable to find group title!');
return htmlspecialchars_decode($ogtitle->content, ENT_QUOTES);
}
private function extractGroupURI($post) {
$elements = $post->find('a')
or returnServerError('Unable to find URI!');
foreach($elements as $anchor) {
// Find the one that is a permalink
if(strpos($anchor->href, 'permalink') !== false) {
return $anchor->href;
}
}
return null;
}
private function extractGroupContent($post) {
$content = $post->find('div.userContent', 0)
or returnServerError('Unable to find user content!');
return $content->innertext . $content->next_sibling()->innertext;
}
private function extractGroupTimestamp($post) {
$element = $post->find('abbr[data-utime]', 0)
or returnServerError('Unable to find timestamp!');
return $element->getAttribute('data-utime');
}
private function extractGroupAuthor($post) {
$element = $post->find('img', 0)
or returnServerError('Unable to find author information!');
return $element->{'aria-label'};
}
private function extractGroupEnclosures($post) {
$elements = $post->find('div.userContent', 0)->next_sibling()->find('img');
$enclosures = array();
foreach($elements as $enclosure) {
$enclosures[] = $enclosure->src;
}
return empty($enclosures) ? null : $enclosures;
}
private function extractGroupTitle($post) {
$element = $post->find('h5', 0)
or returnServerError('Unable to find title!');
if(strpos($element->plaintext, 'shared') === false) {
$content = strip_tags($this->extractGroupContent($post));
return $this->extractGroupAuthor($post)
. ' posted: '
. substr(
$content,
0,
strpos(wordwrap($content, 64), "\n")
)
. '...';
}
return $element->plaintext;
}
#endregion (Group)
#region User
/**
* 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']
. '"!');
}
if(!array_key_exists('path', $urlparts)
|| $urlparts['path'] === '/') {
returnClientError('The URL you provided doesn\'t contain the user name!');
}
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, 'facebook.com/l.php?u=') !== false)
$link = urldecode(extractFromDelimiters($link, 'facebook.com/l.php?u=', '&'));
return ' href="' . $link . '"';
}
}, $content);
}
/**
* Remove Facebook's tracking code
*/
private function remove_tracking_codes($content){
return preg_replace_callback('/ href=\"([^"]+)\"/i', function($matches){
if(is_array($matches) && count($matches) > 1) {
$link = $matches[1];
if(strpos($link, 'facebook.com') !== false) {
if(strpos($link, '?') !== false) {
$link = substr($link, 0, strpos($link, '?'));
}
}
return ' href="' . $link . '"';
}
}, $content);
}
/**
* Convert textual representation of emoticons back to ASCII emoticons.
* i.e. "<i><u>smile emoticon</u></i>" => ":)"
*/
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']))
{
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']);
$http_options = array(
'http' => array(
'method' => 'POST',
'user_agent'=> ini_get('user_agent'),
'header'=>array("Content-type: application/x-www-form-urlencoded\r\nReferer: $captcha_action\r\nCookie: noscript=1\r\n"),
'content' => http_build_query($captcha_fields),
),
$captcha_fields['captcha_response'] = preg_replace('/[^a-zA-Z0-9]+/', '', $_POST['captcha_response']);
$header = array(
'Content-type: application/x-www-form-urlencoded',
'Referer: ' . $captcha_action,
'Cookie: noscript=1'
);
$context = stream_context_create($http_options);
$html = getContents($captcha_action, false, $context);
if ($html === FALSE) { returnServerError('Failed to submit captcha response back to Facebook'); }
unset($_SESSION['captcha_fields']);
$html = str_get_html($html);
$opts = array(
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => http_build_query($captcha_fields)
);
$html = getSimpleHTMLDOM($captcha_action, $header, $opts)
or returnServerError('Failed to submit captcha response back to Facebook');
return $html;
}
unset($_SESSION['captcha_fields']);
unset($_SESSION['captcha_action']);
}
//Retrieve page contents
if (is_null($html)) {
if (!strpos($this->getInput('u'), "/")) {
$html = getSimpleHTMLDOM(self::URI.urlencode($this->getInput('u')).'?_fb_noscript=1')
or returnServerError('No results for this query.');
} else {
$html = getSimpleHTMLDOM(self::URI.'pages/'.$this->getInput('u').'?_fb_noscript=1')
or returnServerError('No results for this query.');
}
return null;
}
private function collectUserData(){
$html = $this->handleCaptchaResponse();
// Retrieve page contents
if(is_null($html)) {
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE'));
$html = getSimpleHTMLDOM($this->getURI(), $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'] = self::URI.$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('HTTP/1.1 500 '.Http::getMessageForCode(500));
header('Content-Type: text/html');
die('<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>');
if (!is_null($captcha)) {
$this->returnCaptchaMessage($captcha);
}
//No captcha? We can carry on retrieving page contents :)
$element = $html->find('#pagelet_timeline_main_column')[0]->children(0)->children(0)->children(0)->next_sibling()->children(0);
// 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.');
}
$element = $html
->find('#pagelet_timeline_main_column')[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;
foreach($element->children() as $post) {
foreach($element->children() as $cell) {
// Manage summary posts
if(strpos($cell->class, '_3xaf') !== false) {
$posts = $cell->children();
} else {
$posts = array($cell);
}
$item = array();
// Optionally skip reviews
if($this->getInput('skip_reviews')
&& !is_null($cell->find('#review_composer_container', 0))) {
continue;
}
if (count($post->find('abbr')) > 0) {
//Retrieve post contents
$content = preg_replace('/(?i)><div class=\"clearfix([^>]+)>(.+?)div\ class=\"userContent\"/i', '', $post);
$content = preg_replace('/(?i)><div class=\"_59tj([^>]+)>(.+?)<\/div><\/div><a/i', '', $content);
$content = preg_replace('/(?i)><div class=\"_3dp([^>]+)>(.+?)div\ class=\"[^u]+userContent\"/i', '', $content);
$content = preg_replace('/(?i)><div class=\"_4l5([^>]+)>(.+?)<\/div>/i', '', $content);
//Remove html nodes, keep only img, links, basic formatting
$content = strip_tags($content,'<a><img><i><u>');
//Adapt link hrefs: convert relative links into absolute links and bypass external link redirection
$content = preg_replace_callback('/ href=\"([^"]+)\"/i', $unescape_fb_link, $content);
//Clean useless html tag properties and fix link closing tags
foreach (array('onmouseover', 'onclick', 'target', 'ajaxify', 'tabindex',
'class', 'style', 'data-[^=]*', 'aria-[^=]*', 'role', 'rel', '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);
//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;
foreach($posts as $post) {
// Check media type
switch($this->getInput('media_type')) {
case 'all': break;
case 'video':
if(empty($post->find('[aria-label=Video]'))) continue 2;
break;
case 'novideo':
if(!empty($post->find('[aria-label=Video]'))) continue 2;
break;
default: break;
}
//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);
if (strlen($title) > 64)
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")).'...';
$item = array();
//Build and add final item
$item['uri'] = self::URI.$post->find('abbr')[0]->parent()->getAttribute('href');
$item['content'] = $content;
$item['title'] = $title;
$item['author'] = $author;
$item['timestamp'] = $date;
$this->items[] = $item;
if(count($post->find('abbr')) > 0) {
$content = $post->find('.userContentWrapper', 0);
// 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',
'',
$content);
$content = preg_replace(
'/(?i)><div class=\"_4l5([^>]+)>(.+?)<\/div>/i',
'',
$content);
// 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>');
$content = $this->unescape_fb_link($content);
// Clean useless html tag properties and fix link closing tags
foreach (array(
'onmouseover',
'onclick',
'target',
'ajaxify',
'tabindex',
'class',
'style',
'data-[^=]*',
'aria-[^=]*',
'role',
'rel',
'id') as $property_name) {
$content = preg_replace('/ ' . $property_name . '=\"[^"]*\"/i', '', $content);
}
$content = preg_replace('/<\/a [^>]+>/i', '</a>', $content);
$this->unescape_fb_emote($content);
// Restore links in the post before further parsing
$post = defaultLinkTo($post, self::URI);
// Restore links in the content before adding to the item
$content = defaultLinkTo($content, self::URI);
$content = $this->remove_tracking_codes($content);
// Retrieve date of the post
$date = $post->find('abbr')[0];
if(isset($date) && $date->hasAttribute('data-utime')) {
$date = $date->getAttribute('data-utime');
} else {
$date = 0;
}
// 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 = $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, 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() {
return (isset($this->authorName) ? $this->authorName.' - ' : '').'Facebook Bridge';
}
}

View File

@@ -1,62 +1,62 @@
<?php
class FeedExpanderExampleBridge extends FeedExpander {
const MAINTAINER = 'logmanoriginal';
const NAME = 'FeedExpander Example';
const URI = '#';
const DESCRIPTION = 'Example bridge to test FeedExpander';
const MAINTAINER = 'logmanoriginal';
const NAME = 'FeedExpander Example';
const URI = 'http://github.com/RSS-Bridge/rss-bridge/';
const DESCRIPTION = 'Example bridge to test FeedExpander';
const PARAMETERS = array(
'Feed' => array(
'version' => array(
'name' => 'Version',
'type' => 'list',
'required' => true,
'title' => 'Select your feed format/version',
'defaultValue' => 'RSS 2.0',
'values' => array(
'RSS 0.91' => 'rss_0_9_1',
'RSS 1.0' => 'rss_1_0',
'RSS 2.0' => 'rss_2_0',
'ATOM 1.0' => 'atom_1_0'
)
)
)
);
const PARAMETERS = array(
'Feed' => array(
'version' => array(
'name' => 'Version',
'type' => 'list',
'required' => true,
'title' => 'Select your feed format/version',
'defaultValue' => 'RSS 2.0',
'values' => array(
'RSS 0.91' => 'rss_0_9_1',
'RSS 1.0' => 'rss_1_0',
'RSS 2.0' => 'rss_2_0',
'ATOM 1.0' => 'atom_1_0'
)
)
)
);
public function collectData(){
switch($this->getInput('version')){
case 'rss_0_9_1':
parent::collectExpandableDatas('http://static.userland.com/gems/backend/sampleRss.xml');
break;
case 'rss_1_0':
parent::collectExpandableDatas('http://feeds.nature.com/nature/rss/current?format=xml');
break;
case 'rss_2_0':
parent::collectExpandableDatas('http://feeds.rssboard.org/rssboard?format=xml');
break;
case 'atom_1_0':
parent::collectExpandableDatas('http://segfault.linuxmint.com/feed/atom/');
break;
default: returnClientError('Unknown version ' . $this->getInput('version') . '!');
}
}
public function collectData(){
switch($this->getInput('version')) {
case 'rss_0_9_1':
parent::collectExpandableDatas('http://static.userland.com/gems/backend/sampleRss.xml');
break;
case 'rss_1_0':
parent::collectExpandableDatas('http://feeds.nature.com/nature/rss/current?format=xml');
break;
case 'rss_2_0':
parent::collectExpandableDatas('http://feeds.rssboard.org/rssboard?format=xml');
break;
case 'atom_1_0':
parent::collectExpandableDatas('http://segfault.linuxmint.com/feed/atom/');
break;
default: returnClientError('Unknown version ' . $this->getInput('version') . '!');
}
}
protected function parseItem($newsItem) {
switch($this->getInput('version')){
case 'rss_0_9_1':
return $this->parseRSS_0_9_1_Item($newsItem);
break;
case 'rss_1_0':
return $this->parseRSS_1_0_Item($newsItem);
break;
case 'rss_2_0':
return $this->parseRSS_2_0_Item($newsItem);
break;
case 'atom_1_0':
return $this->parseATOMItem($newsItem);
break;
default: returnClientError('Unknown version ' . $this->getInput('version') . '!');
}
}
protected function parseItem($newsItem) {
switch($this->getInput('version')) {
case 'rss_0_9_1':
return $this->parseRSS_0_9_1_Item($newsItem);
break;
case 'rss_1_0':
return $this->parseRSS_1_0_Item($newsItem);
break;
case 'rss_2_0':
return $this->parseRSS_2_0_Item($newsItem);
break;
case 'atom_1_0':
return $this->parseATOMItem($newsItem);
break;
default: returnClientError('Unknown version ' . $this->getInput('version') . '!');
}
}
}

View File

@@ -1,23 +1,33 @@
<?php
class FierPandaBridge extends BridgeAbstract {
const MAINTAINER = "snroki";
const NAME = "Fier Panda Bridge";
const URI = "http://www.fier-panda.fr/";
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = "Returns latest articles from Fier Panda.";
const MAINTAINER = 'snroki';
const NAME = 'Fier Panda Bridge';
const URI = 'http://www.fier-panda.fr/';
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'Returns latest articles from Fier Panda.';
public function collectData(){
$html = getSimpleHTMLDOM(self::URI) or returnServerError('Could not request Fier Panda.');
public function getIcon() {
return self::URI . 'wp-content/themes/fier-panda/img/favicon.png';
}
foreach($html->find('div.container-content article') as $element) {
$item = array();
$item['uri'] = $this->getURI().$element->find('a', 0)->href;
$item['title'] = trim($element->find('h1 a', 0)->innertext);
// Remove the link at the end of the article
$element->find('p a', 0)->outertext = '';
$item['content'] = $element->find('p', 0)->innertext;
$this->items[] = $item;
}
}
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request Fier Panda.');
defaultLinkTo($html, static::URI);
foreach($html->find('article') as $article) {
$item = array();
$item['uri'] = $article->find('a', 0)->href;
$item['title'] = $article->find('a', 0)->title;
$this->items[] = $item;
}
}
}

101
bridges/FilterBridge.php Normal file
View File

@@ -0,0 +1,101 @@
<?php
class FilterBridge extends FeedExpander {
const MAINTAINER = 'Frenzie';
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(
'name' => 'Feed URL',
'required' => true,
),
'filter' => array(
'name' => 'Filter item title (regular expression)',
'required' => false,
),
'filter_type' => array(
'name' => 'Filter type',
'type' => 'list',
'required' => false,
'values' => array(
'Permit' => 'permit',
'Block' => 'block',
),
'defaultValue' => 'permit',
),
'title_from_content' => array(
'name' => 'Generate title from content',
'type' => 'checkbox',
'required' => false,
)
));
protected function parseItem($newItem){
$item = parent::parseItem($newItem);
if($this->getInput('title_from_content') && array_key_exists('content', $item)) {
$content = str_get_html($item['content']);
$pos = strpos($item['content'], ' ', 50);
$item['title'] = substr(
$content->plaintext,
0,
$pos
);
if(strlen($content->plaintext) >= $pos) {
$item['title'] .= '...';
}
}
switch(true) {
case $this->getFilterType() === 'permit':
if (preg_match($this->getFilter(), $item['title'])) {
return $item;
}
break;
case $this->getFilterType() === 'block':
if (!preg_match($this->getFilter(), $item['title'])) {
return $item;
}
break;
}
return null;
}
protected function getFilter(){
return '/' . $this->getInput('filter') . '/';
}
protected function getFilterType(){
return $this->getInput('filter_type');
}
public function getURI(){
$url = $this->getInput('url');
if(empty($url)) {
$url = parent::getURI();
}
return $url;
}
public function collectData(){
if($this->getInput('url') && substr($this->getInput('url'), 0, strlen('http')) !== 'http') {
// just in case someone find a way to access local files by playing with the url
returnClientError('The url parameter must either refer to http or https protocol.');
}
try{
$this->collectExpandableDatas($this->getURI());
} 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;
}
}

185
bridges/FlickrBridge.php Normal file
View File

@@ -0,0 +1,185 @@
<?php
/* This is a mashup of FlickrExploreBridge by sebsauvage and FlickrTagBridge
* by erwang, providing the functionality of both in one.
*/
class FlickrBridge extends BridgeAbstract {
const MAINTAINER = 'logmanoriginal';
const NAME = 'Flickr Bridge';
const URI = 'https://www.flickr.com/';
const CACHE_TIMEOUT = 21600; // 6 hours
const DESCRIPTION = 'Returns images from Flickr';
const PARAMETERS = array(
'Explore' => array(),
'By keyword' => array(
'q' => array(
'name' => 'Keyword',
'type' => 'text',
'required' => true,
'title' => 'Insert keyword',
'exampleValue' => 'bird'
)
),
'By username' => array(
'u' => array(
'name' => 'Username',
'type' => 'text',
'required' => true,
'title' => 'Insert username (as shown in the address bar)',
'exampleValue' => 'flickr'
)
)
);
public function collectData(){
switch($this->queriedContext) {
case 'Explore':
$filter = 'photo-lite-models';
$html = getSimpleHTMLDOM(self::URI . 'explore')
or returnServerError('Could not request Flickr.');
break;
case 'By keyword':
$filter = 'photo-lite-models';
$html = getSimpleHTMLDOM(self::URI . 'search/?q=' . urlencode($this->getInput('q')) . '&s=rec')
or returnServerError('No results for this query.');
break;
case 'By username':
$filter = 'photo-models';
$html = getSimpleHTMLDOM(self::URI . 'photos/' . urlencode($this->getInput('u')))
or returnServerError('Requested username can\'t be found.');
break;
default:
returnClientError('Invalid context: ' . $this->queriedContext);
}
$model_json = $this->extractJsonModel($html);
$photo_models = $this->getPhotoModels($model_json, $filter);
foreach($photo_models as $model) {
$item = array();
/* Author name depends on scope. On a keyword search the
* author is part of the picture data. On a username search
* the author is part of the owner data.
*/
if(array_key_exists('username', $model)) {
$item['author'] = $model['username'];
} elseif (array_key_exists('owner', reset($model_json)[0])) {
$item['author'] = reset($model_json)[0]['owner']['username'];
}
$item['title'] = (array_key_exists('title', $model) ? $model['title'] : 'Untitled');
$item['uri'] = self::URI . 'photo.gne?id=' . $model['id'];
$description = (array_key_exists('description', $model) ? $model['description'] : '');
$item['content'] = '<a href="'
. $item['uri']
. '"><img src="'
. $this->extractContentImage($model)
. '" style="max-width: 640px; max-height: 480px;"/></a><br><p>'
. $description
. '</p>';
$item['enclosures'] = $this->extractEnclosures($model);
$this->items[] = $item;
}
}
private function extractJsonModel($html) {
// Find SCRIPT containing JSON data
$model = $html->find('.modelExport', 0);
$model_text = $model->innertext;
// Find start and end of JSON data
$start = strpos($model_text, 'modelExport:') + strlen('modelExport:');
$end = strpos($model_text, 'auth:') - strlen('auth:');
// Extract JSON data, remove trailing comma
$model_text = trim(substr($model_text, $start, $end - $start));
$model_text = substr($model_text, 0, strlen($model_text) - 1);
return json_decode($model_text, true);
}
private function getPhotoModels($json, $filter) {
// The JSON model contains a "legend" array, where each element contains
// the path to an element in the "main" object
$photo_models = array();
foreach($json['legend'] as $legend) {
$photo_model = $json['main'];
foreach($legend as $element) { // Traverse tree
$photo_model = $photo_model[$element];
}
// We are only interested in content
if($photo_model['_flickrModelRegistry'] === $filter) {
$photo_models[] = $photo_model;
}
}
return $photo_models;
}
private function extractEnclosures($model) {
$areas = array();
foreach($model['sizes'] as $size) {
$areas[$size['width'] * $size['height']] = $size['url'];
}
return array($this->fixURL(max($areas)));
}
private function extractContentImage($model) {
$areas = array();
$limit = 320 * 240;
foreach($model['sizes'] as $size) {
$image_area = $size['width'] * $size['height'];
if($image_area >= $limit) {
$areas[$image_area] = $size['url'];
}
}
return $this->fixURL(min($areas));
}
private function fixURL($url) {
// For some reason the image URLs don't include the protocol (https)
if(strpos($url, '//') === 0) {
$url = 'https:' . $url;
}
return $url;
}
}

View File

@@ -1,42 +0,0 @@
<?php
class FlickrExploreBridge extends BridgeAbstract{
const MAINTAINER = "sebsauvage";
const NAME = "Flickr Explore";
const URI = "https://www.flickr.com/";
const CACHE_TIMEOUT = 21600; // 6
const DESCRIPTION = "Returns the latest interesting images from Flickr";
public function collectData(){
$html = getSimpleHTMLDOM(self::URI.'explore')
or returnServerError('Could not request Flickr.');
foreach($html->find('.photo-list-photo-view') as $element) {
// Get the styles
$style = explode(';', $element->style);
// Get the background-image style
$backgroundImage = explode(':', end($style));
// URI type : url(//cX.staticflickr.com/X/XXXXX/XXXXXXXXX.jpg)
$imageURI = trim(str_replace(['url(', ')'], '', end($backgroundImage)));
// Get the image ID
$imageURIs = explode('_', basename($imageURI));
$imageID = reset($imageURIs);
// Get the image JSON via Flickr API
$imageJSON = json_decode(getContents(
'https://api.flickr.com/services/rest/?'
.'method=flickr.photos.getInfo&'
.'api_key=103b574d49bd51f0e18bfe907da44a0f&'
.'photo_id='.$imageID.'&'
.'format=json&'
.'nojsoncallback=1'
)) or returnServerError('Could not request Flickr.'); // FIXME: Request time too long...
$item = array();
$item['uri'] = self::URI.'photo.gne?id='.$imageID;
$item['content'] = '<a href="' . $item['uri'] . '"><img src="' . $imageURI . '" /></a>'; // FIXME: Filter javascript ?
$item['title'] = $imageJSON->photo->title->_content;
$this->items[] = $item;
}
}
}

View File

@@ -1,48 +0,0 @@
<?php
class FlickrTagBridge extends BridgeAbstract{
const MAINTAINER = "erwang";
const NAME = "Flickr TagUser";
const URI = "http://www.flickr.com/";
const CACHE_TIMEOUT = 21600; //6h
const DESCRIPTION = "Returns the tagged or user images from Flickr";
const PARAMETERS = array(
'By keyword' => array(
'q'=>array(
'name'=>'keyword',
'required'=>true
)
),
'By username' => array(
'u'=>array(
'name'=>'Username',
'required'=>true
)
),
);
public function collectData(){
switch($this->queriedContext){
case 'By keyword':
$html = getSimpleHTMLDOM(self::URI.'search/?q='.urlencode($this->getInput('q')).'&s=rec')
or returnServerError('No results for this query.');
break;
case 'by username':
$html = getSimpleHTMLDOM(self::URI.'photos/'.urlencode($this->getInput('u')).'/')
or returnServerError('Requested username can\'t be found.');
break;
}
foreach($html->find('span.photo_container') as $element) {
$item = array();
$item['uri'] = self::URI.$element->find('a',0)->href;
$thumbnailUri = $element->find('img',0)->getAttribute('data-defer-src');
$item['content'] = '<a href="' . $item['uri'] . '"><img src="' . $thumbnailUri . '" /></a>'; // FIXME: Filter javascript ?
$item['title'] = $element->find('a',0)->title;
$this->items[] = $item;
}
}
}

View File

@@ -1,40 +1,75 @@
<?php
class FootitoBridge extends BridgeAbstract{
class FootitoBridge extends BridgeAbstract {
const MAINTAINER = "superbaillot.net";
const NAME = "Footito";
const URI = "http://www.footito.fr/";
const DESCRIPTION = "Footito";
const MAINTAINER = 'superbaillot.net';
const NAME = 'Footito';
const URI = 'http://www.footito.fr/';
const DESCRIPTION = 'Footito';
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request Footito.');
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request Footito.');
foreach($html->find('div.post') as $element) {
$item = array();
foreach($html->find('div.post') as $element) {
$item = array();
$content = trim($element->innertext);
$content = str_replace("<img", "<img style='float : left;'", $content );
$content = str_replace("class=\"logo\"", "style='float : left;'", $content );
$content = str_replace("class=\"contenu\"", "style='margin-left : 60px;'", $content );
$content = str_replace("class=\"responsive-comment\"", "style='border-top : 1px #DDD solid; background-color : white; padding : 10px;'", $content );
$content = str_replace("class=\"jaime\"", "style='display : none;'", $content );
$content = str_replace("class=\"auteur-event responsive\"", "style='display : none;'", $content );
$content = str_replace("class=\"report-abuse-button\"", "style='display : none;'", $content );
$content = str_replace("class=\"reaction clearfix\"", "style='margin : 10px 0px; padding : 5px; border-bottom : 1px #DDD solid;'", $content );
$content = str_replace("class=\"infos\"", "style='font-size : 0.7em;'", $content );
$content = trim($element->innertext);
$content = str_replace(
'<img',
"<img style='float : left;'",
$content );
$item['content'] = $content;
$content = str_replace(
'class="logo"',
"style='float : left;'",
$content );
$title = $element->find('.contenu .texte ', 0)->plaintext;
$item['title'] = $title;
$content = str_replace(
'class="contenu"',
"style='margin-left : 60px;'",
$content );
$info = $element->find('div.infos', 0);
$content = str_replace(
'class="responsive-comment"',
"style='border-top : 1px #DDD solid; background-color : white; padding : 10px;'",
$content );
$item['timestamp'] = strtotime($info->find('time', 0)->datetime);
$item['author'] = $info->find('a.auteur', 0)->plaintext;
$content = str_replace(
'class="jaime"',
"style='display : none;'",
$content );
$this->items[] = $item;
}
}
$content = str_replace(
'class="auteur-event responsive"',
"style='display : none;'",
$content );
$content = str_replace(
'class="report-abuse-button"',
"style='display : none;'",
$content );
$content = str_replace(
'class="reaction clearfix"',
"style='margin : 10px 0px; padding : 5px; border-bottom : 1px #DDD solid;'",
$content );
$content = str_replace(
'class="infos"',
"style='font-size : 0.7em;'",
$content );
$item['content'] = $content;
$title = $element->find('.contenu .texte ', 0)->plaintext;
$item['title'] = $title;
$info = $element->find('div.infos', 0);
$item['timestamp'] = strtotime($info->find('time', 0)->datetime);
$item['author'] = $info->find('a.auteur', 0)->plaintext;
$this->items[] = $item;
}
}
}

40
bridges/ForGifsBridge.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
class ForGifsBridge extends FeedExpander {
const MAINTAINER = 'logmanoriginal';
const NAME = 'forgifs Bridge';
const URI = 'https://forgifs.com';
const DESCRIPTION = 'Returns the forgifs feed with actual gifs instead of images';
public function collectData() {
$this->collectExpandableDatas('https://forgifs.com/gallery/srss/7');
}
protected function parseItem($feedItem) {
$item = parent::parseItem($feedItem);
$content = str_get_html($item['content']);
$img = $content->find('img', 0);
$poster = $img->src;
// The actual gif is the same path but its id must be decremented by one.
// Example:
// http://forgifs.com/gallery/d/279419-2/Reporter-videobombed-shoulder-checks.gif
// http://forgifs.com/gallery/d/279418-2/Reporter-videobombed-shoulder-checks.gif
// Notice how this changes ----------^
// Now let's extract that number and do some math
// Notice: Technically we could also load the content page but that would
// require unnecessary traffic. As long as it works...
$num = substr($img->src, 29, 6);
$num -= 1;
$img->src = substr_replace($img->src, $num, 29, strlen($num));
$img->width = 'auto';
$img->height = 'auto';
$item['content'] = $content;
return $item;
}
}

View File

@@ -1,68 +1,78 @@
<?php
class FourchanBridge extends BridgeAbstract{
class FourchanBridge extends BridgeAbstract {
const MAINTAINER = "mitsukarenai";
const NAME = "4chan";
const URI = "https://boards.4chan.org/";
const MAINTAINER = 'mitsukarenai';
const NAME = '4chan';
const URI = 'https://boards.4chan.org/';
const CACHE_TIMEOUT = 300; // 5min
const DESCRIPTION = "Returns posts from the specified thread";
const DESCRIPTION = 'Returns posts from the specified thread';
const PARAMETERS = array( array(
'c'=>array(
'name'=>'Thread category',
'required'=>true
),
't'=>array(
'name'=>'Thread number',
'type'=>'number',
'required'=>true
)
));
const PARAMETERS = array( array(
'c' => array(
'name' => 'Thread category',
'required' => true
),
't' => array(
'name' => 'Thread number',
'type' => 'number',
'required' => true
)
));
public function getURI(){
return static::URI.$this->getInput('c').'/thread/'.$this->getInput('t');
}
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError("Could not request 4chan, thread not found");
foreach($html->find('div.postContainer') as $element) {
$item = array();
$item['id'] = $element->find('.post', 0)->getAttribute('id');
$item['uri'] = $this->getURI().'#'.$item['id'];
$item['timestamp'] = $element->find('span.dateTime', 0)->getAttribute('data-utc');
$item['author'] = $element->find('span.name', 0)->plaintext;
$file=$element->find('.file', 0);
if(!empty($file) ) {
$item['image'] = $element->find('.file a', 0)->href;
$item['imageThumb'] = $element->find('.file img', 0)->src;
if(!isset($item['imageThumb']) and strpos($item['image'], '.swf') !== FALSE)
$item['imageThumb'] = 'http://i.imgur.com/eO0cxf9.jpg';
}
if(!empty($element->find('span.subject', 0)->innertext )) {
$item['subject'] = $element->find('span.subject', 0)->innertext;
public function getURI(){
if(!is_null($this->getInput('c')) && !is_null($this->getInput('t'))) {
return static::URI . $this->getInput('c') . '/thread/' . $this->getInput('t');
}
$item['title'] = 'reply '.$item['id'].' | '.$item['author'];
if(isset($item['subject'])){
$item['title'] = $item['subject'].' - '.$item['title'];
}
$content = $element->find('.postMessage', 0)->innertext;
$content = str_replace('href="#p','href="'.$this->getURI().'#p',$content);
$item['content'] = '<span id="'.$item['id'].'">'.$content.'</span>';
if(isset($item['image'])){
$item['content'] = '<a href="'.$item['image'].'">'
.'<img alt="'.$item['id'].'" src="'.$item['imageThumb'].'" />'
.'</a><br>'
.$item['content'];
}
$this->items[] = $item;
return parent::getURI();
}
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request 4chan, thread not found');
foreach($html->find('div.postContainer') as $element) {
$item = array();
$item['id'] = $element->find('.post', 0)->getAttribute('id');
$item['uri'] = $this->getURI() . '#' . $item['id'];
$item['timestamp'] = $element->find('span.dateTime', 0)->getAttribute('data-utc');
$item['author'] = $element->find('span.name', 0)->plaintext;
$file = $element->find('.file', 0);
if(!empty($file)) {
$item['image'] = $element->find('.file a', 0)->href;
$item['imageThumb'] = $element->find('.file img', 0)->src;
if(!isset($item['imageThumb']) and strpos($item['image'], '.swf') !== false)
$item['imageThumb'] = 'http://i.imgur.com/eO0cxf9.jpg';
}
if(!empty($element->find('span.subject', 0)->innertext)) {
$item['subject'] = $element->find('span.subject', 0)->innertext;
}
$item['title'] = 'reply ' . $item['id'] . ' | ' . $item['author'];
if(isset($item['subject'])) {
$item['title'] = $item['subject'] . ' - ' . $item['title'];
}
$content = $element->find('.postMessage', 0)->innertext;
$content = str_replace('href="#p', 'href="' . $this->getURI() . '#p', $content);
$item['content'] = '<span id="' . $item['id'] . '">' . $content . '</span>';
if(isset($item['image'])) {
$item['content'] = '<a href="'
. $item['image']
. '"><img alt="'
. $item['id']
. '" src="'
. $item['imageThumb']
. '" /></a><br>'
. $item['content'];
}
$this->items[] = $item;
}
$this->items = array_reverse($this->items);
}
$this->items = array_reverse($this->items);
}
}

View File

@@ -1,166 +1,144 @@
<?php
class FuturaSciencesBridge extends FeedExpander {
const MAINTAINER = 'ORelio';
const NAME = 'Futura-Sciences Bridge';
const URI = 'http://www.futura-sciences.com/';
const DESCRIPTION = 'Returns the newest articles.';
const MAINTAINER = 'ORelio';
const NAME = 'Futura-Sciences Bridge';
const URI = 'https://www.futura-sciences.com/';
const DESCRIPTION = 'Returns the newest articles.';
const PARAMETERS = array( array(
'feed'=> array(
'name'=>'Feed',
'type'=>'list',
'values'=>array(
'Les flux multi-magazines'=>array(
'Les dernières actualités de Futura-Sciences'=>'actualites',
'Les dernières définitions de Futura-Sciences'=>'definitions',
'Les dernières photos de Futura-Sciences'=>'photos',
'Les dernières questions - réponses de Futura-Sciences'=>'questions-reponses',
'Les derniers dossiers de Futura-Sciences'=>'dossiers'
),
'Les flux Services'=> array(
'Les cartes virtuelles de Futura-Sciences'=>'services/cartes-virtuelles',
'Les fonds d\'écran de Futura-Sciences'=>'services/fonds-ecran'
),
'Les flux Santé'=>array(
'Les dernières actualités de Futura-Santé'=>'sante/actualites',
'Les dernières définitions de Futura-Santé'=>'sante/definitions',
'Les dernières questions-réponses de Futura-Santé'=>'sante/question-reponses',
'Les derniers dossiers de Futura-Santé'=>'sante/dossiers'
),
'Les flux High-Tech'=>array(
'Les dernières actualités de Futura-High-Tech'=>'high-tech/actualites',
'Les dernières astuces de Futura-High-Tech'=>'high-tech/question-reponses',
'Les dernières définitions de Futura-High-Tech'=>'high-tech/definitions',
'Les derniers dossiers de Futura-High-Tech'=>'high-tech/dossiers'
),
'Les flux Espace'=>array(
'Les dernières actualités de Futura-Espace'=>'espace/actualites',
'Les dernières définitions de Futura-Espace'=>'espace/definitions',
'Les dernières questions-réponses de Futura-Espace'=>'espace/question-reponses',
'Les derniers dossiers de Futura-Espace'=>'espace/dossiers'
),
'Les flux Environnement'=>array(
'Les dernières actualités de Futura-Environnement'=>'environnement/actualites',
'Les dernières définitions de Futura-Environnement'=>'environnement/definitions',
'Les dernières questions-réponses de Futura-Environnement'=>'environnement/question-reponses',
'Les derniers dossiers de Futura-Environnement'=>'environnement/dossiers'
),
'Les flux Maison'=>array(
'Les dernières actualités de Futura-Maison'=>'maison/actualites',
'Les dernières astuces de Futura-Maison'=>'maison/question-reponses',
'Les dernières définitions de Futura-Maison'=>'maison/definitions',
'Les derniers dossiers de Futura-Maison'=>'maison/dossiers'
),
'Les flux Nature'=>array(
'Les dernières actualités de Futura-Nature'=>'nature/actualites',
'Les dernières définitions de Futura-Nature'=>'nature/definitions',
'Les dernières questions-réponses de Futura-Nature'=>'nature/question-reponses',
'Les derniers dossiers de Futura-Nature'=>'nature/dossiers'
),
'Les flux Terre'=>array(
'Les dernières actualités de Futura-Terre'=>'terre/actualites',
'Les dernières définitions de Futura-Terre'=>'terre/definitions',
'Les dernières questions-réponses de Futura-Terre'=>'terre/question-reponses',
'Les derniers dossiers de Futura-Terre'=>'terre/dossiers'
),
'Les flux Matière'=>array(
'Les dernières actualités de Futura-Matière'=>'matiere/actualites',
'Les dernières définitions de Futura-Matière'=>'matiere/definitions',
'Les dernières questions-réponses de Futura-Matière'=>'matiere/question-reponses',
'Les derniers dossiers de Futura-Matière'=>'matiere/dossiers'
),
'Les flux Mathématiques'=>array(
'Les dernières actualités de Futura-Mathématiques'=>'mathematiques/actualites',
'Les derniers dossiers de Futura-Mathématiques'=>'mathematiques/dossiers'
)
)
)
));
const PARAMETERS = array( array(
'feed' => array(
'name' => 'Feed',
'type' => 'list',
'values' => array(
'Les flux multi-magazines' => array(
'Les dernières actualités de Futura-Sciences' => 'actualites',
'Les dernières définitions de Futura-Sciences' => 'definitions',
'Les dernières photos de Futura-Sciences' => 'photos',
'Les dernières questions - réponses de Futura-Sciences' => 'questions-reponses',
'Les derniers dossiers de Futura-Sciences' => 'dossiers'
),
'Les flux Services' => array(
'Les cartes virtuelles de Futura-Sciences' => 'services/cartes-virtuelles',
'Les fonds d\'écran de Futura-Sciences' => 'services/fonds-ecran'
),
'Les flux Santé' => array(
'Les dernières actualités de Futura-Santé' => 'sante/actualites',
'Les dernières définitions de Futura-Santé' => 'sante/definitions',
'Les dernières questions-réponses de Futura-Santé' => 'sante/question-reponses',
'Les derniers dossiers de Futura-Santé' => 'sante/dossiers'
),
'Les flux High-Tech' => array(
'Les dernières actualités de Futura-High-Tech' => 'high-tech/actualites',
'Les dernières astuces de Futura-High-Tech' => 'high-tech/question-reponses',
'Les dernières définitions de Futura-High-Tech' => 'high-tech/definitions',
'Les derniers dossiers de Futura-High-Tech' => 'high-tech/dossiers'
),
'Les flux Espace' => array(
'Les dernières actualités de Futura-Espace' => 'espace/actualites',
'Les dernières définitions de Futura-Espace' => 'espace/definitions',
'Les dernières questions-réponses de Futura-Espace' => 'espace/question-reponses',
'Les derniers dossiers de Futura-Espace' => 'espace/dossiers'
),
'Les flux Environnement' => array(
'Les dernières actualités de Futura-Environnement' => 'environnement/actualites',
'Les dernières définitions de Futura-Environnement' => 'environnement/definitions',
'Les dernières questions-réponses de Futura-Environnement' => 'environnement/question-reponses',
'Les derniers dossiers de Futura-Environnement' => 'environnement/dossiers'
),
'Les flux Maison' => array(
'Les dernières actualités de Futura-Maison' => 'maison/actualites',
'Les dernières astuces de Futura-Maison' => 'maison/question-reponses',
'Les dernières définitions de Futura-Maison' => 'maison/definitions',
'Les derniers dossiers de Futura-Maison' => 'maison/dossiers'
),
'Les flux Nature' => array(
'Les dernières actualités de Futura-Nature' => 'nature/actualites',
'Les dernières définitions de Futura-Nature' => 'nature/definitions',
'Les dernières questions-réponses de Futura-Nature' => 'nature/question-reponses',
'Les derniers dossiers de Futura-Nature' => 'nature/dossiers'
),
'Les flux Terre' => array(
'Les dernières actualités de Futura-Terre' => 'terre/actualites',
'Les dernières définitions de Futura-Terre' => 'terre/definitions',
'Les dernières questions-réponses de Futura-Terre' => 'terre/question-reponses',
'Les derniers dossiers de Futura-Terre' => 'terre/dossiers'
),
'Les flux Matière' => array(
'Les dernières actualités de Futura-Matière' => 'matiere/actualites',
'Les dernières définitions de Futura-Matière' => 'matiere/definitions',
'Les dernières questions-réponses de Futura-Matière' => 'matiere/question-reponses',
'Les derniers dossiers de Futura-Matière' => 'matiere/dossiers'
),
'Les flux Mathématiques' => array(
'Les dernières actualités de Futura-Mathématiques' => 'mathematiques/actualites',
'Les derniers dossiers de Futura-Mathématiques' => 'mathematiques/dossiers'
)
)
)
));
public function collectData(){
$url = self::URI . 'rss/' . $this->getInput('feed') . '.xml';
$this->collectExpandableDatas($url, 10);
}
public function collectData(){
$url = self::URI . 'rss/' . $this->getInput('feed') . '.xml';
$this->collectExpandableDatas($url, 10);
}
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
$item['uri'] = str_replace('#xtor=RSS-8', '', $item['uri']);
$article = getSimpleHTMLDOMCached($item['uri'])
or returnServerError('Could not request Futura-Sciences: ' . $item['uri']);
$item['content'] = $this->ExtractArticleContent($article);
$item['author'] = empty($this->ExtractAuthor($article)) ? $item['author'] : $this->ExtractAuthor($article);
return $item;
}
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
$item['uri'] = str_replace('#xtor=RSS-8', '', $item['uri']);
$article = getSimpleHTMLDOMCached($item['uri'])
or returnServerError('Could not request Futura-Sciences: ' . $item['uri']);
$item['content'] = $this->extractArticleContent($article);
$author = $this->extractAuthor($article);
if (!empty($author))
$item['author'] = $author;
return $item;
}
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 extractArticleContent($article){
$contents = $article->find('section.article-text-classic', 0)->innertext;
$headline = trim($article->find('p.description', 0)->plaintext);
if(!empty($headline))
$headline = '<p><b>' . $headline . '</b></p>';
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;
}
foreach (array(
'<div class="clear',
'<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',
'<div class="toolbar noprint',
'<div class="addthis_toolbox',
'<div class="noprint',
'<div class="bg bglight border border-full noprint',
'<div class="httplogbar-wrapper noprint',
'<div id="forumcomments',
'<div ng-if="active"'
) as $div_start) {
$contents = stripRecursiveHTMLSection($contents, 'div', $div_start);
}
function ExtractArticleContent($article){
$contents = $article->find('div.content', 0)->innertext;
$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>');
foreach (array(
'<div class="clear',
'<div class="sharebar2',
'<div class="diaporamafullscreen"',
'<div style="margin-bottom:10px;" class="noprint"',
'<div class="ficheprevnext',
'<div class="bar noprint',
'<div class="toolbar noprint',
'<div class="addthis_toolbox',
'<div class="noprint',
'<div class="bg bglight border border-full noprint',
'<div class="httplogbar-wrapper noprint',
'<div id="forumcomments'
) as $div_start) {
$contents = $this->StripRecursiveHTMLSection($contents , 'div', $div_start);
}
return $headline . trim($contents);
}
$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="', '"');
return $contents;
}
// Extracts the author from an article or element
function ExtractAuthor($article){
$article_author = $article->find('span.author', 0);
if($article_author){
return trim(str_replace(', Futura-Sciences', '', $article_author->plaintext));
}
return '';
}
// Extracts the author from an article or element
private function extractAuthor($article){
$article_author = $article->find('h3.epsilon', 0);
if($article_author) {
return trim(str_replace(', Futura-Sciences', '', $article_author->plaintext));
}
return '';
}
}

View File

@@ -1,124 +1,151 @@
<?php
class GBAtempBridge extends BridgeAbstract {
const MAINTAINER = 'ORelio';
const NAME = 'GBAtemp';
const URI = 'http://gbatemp.net/';
const DESCRIPTION = 'GBAtemp is a user friendly underground video game community.';
const MAINTAINER = 'ORelio';
const NAME = 'GBAtemp';
const URI = 'https://gbatemp.net/';
const DESCRIPTION = 'GBAtemp is a user friendly underground video game community.';
const PARAMETERS = array( array(
'type'=>array(
'name'=>'Type',
'type'=>'list',
'required'=>true,
'values'=>array(
'News'=>'N',
'Reviews'=>'R',
'Tutorials'=>'T',
'Forum'=>'F'
)
)
));
const PARAMETERS = array( array(
'type' => array(
'name' => 'Type',
'type' => 'list',
'required' => true,
'values' => array(
'News' => 'N',
'Reviews' => 'R',
'Tutorials' => 'T',
'Forum' => 'F'
)
)
));
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 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 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 cleanupPostContent($content, $site_url){
$content = str_replace(':arrow:', '&#x27a4;', $content);
$content = str_replace('href="attachments/', 'href="' . $site_url . 'attachments/', $content);
$content = stripWithDelimiters($content, '<script', '</script>');
return $content;
}
private function build_item($uri, $title, $author, $timestamp, $content) {
$item = array();
$item['uri'] = $uri;
$item['title'] = $title;
$item['author'] = $author;
$item['timestamp'] = $timestamp;
$item['content'] = $content;
return $item;
}
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 cleanup_post_content($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>');
return $content;
}
private function fetchPostContent($uri, $site_url){
$html = getSimpleHTMLDOMCached($uri);
if(!$html) {
return 'Could not request GBAtemp: ' . $uri;
}
private function fetch_post_content($uri, $site_url) {
$html = getSimpleHTMLDOM($uri);
if(!$html){
return 'Could not request GBAtemp '.$uri;
}
$content = $html->find('div.messageContent, blockquote.baseHtml', 0)->innertext;
return $this->cleanupPostContent($content, $site_url);
}
$content = $html->find('div.messageContent', 0)->innertext;
return $this->cleanup_post_content($content, $site_url);
}
public function collectData(){
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request GBAtemp.');
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request GBAtemp.');
switch($this->getInput('type')) {
case 'N':
foreach($html->find('li[class=news_item full]') as $newsItem) {
$url = self::URI . $newsItem->find('a', 0)->href;
$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, $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 = $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, $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 = $this->findItemDate($tutorialItem);
$author = $tutorialItem->find('a.username', 0)->plaintext;
$content = $this->fetchPostContent($url, self::URI);
$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 = $this->findItemDate($postItem);
$author = $postItem->find('a.username', 0)->plaintext;
$content = $this->fetchPostContent($url, self::URI);
$this->items[] = $this->buildItem($url, $title, $author, $time, null, $content);
unset($postItem); // Free up memory
}
break;
}
}
switch($this->getInput('type')){
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="', '"'));
$author = $newsItem->find('a.username', 0)->plaintext;
$title = $newsItem->find('a', 1)->plaintext;
$content = $this->fetch_post_content($url, self::URI);
$this->items[] = $this->build_item($url, $title, $author, $time, $content);
}
case 'R':
foreach ($html->find('li.portal_review') as $reviewItem) {
$url = self::URI.$reviewItem->find('a', 0)->href;
$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="', '"'));
$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->cleanup_post_content($intro.$review.$subheader.$procons.$scores, self::URI);
$this->items[] = $this->build_item($url, $title, $author, $time, $content);
}
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="', '"'));
$author = $tutorialItem->find('a.username', 0)->plaintext;
$content = $this->fetch_post_content($url, self::URI);
$this->items[] = $this->build_item($url, $title, $author, $time, $content);
}
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="', '"'));
$author = $postItem->find('a.username', 0)->plaintext;
$content = $this->fetch_post_content($url, self::URI);
$this->items[] = $this->build_item($url, $title, $author, $time, $content);
}
}
}
public function getName() {
if(!is_null($this->getInput('type'))) {
$type = array_search(
$this->getInput('type'),
self::PARAMETERS[$this->queriedContext]['type']['values']
);
return 'GBAtemp ' . $type . ' Bridge';
}
public function getName() {
$type=array_search(
$this->getInput('type'),
self::PARAMETERS[$this->queriedContext]['type']['values']
);
return 'GBAtemp '.$type.' Bridge';
}
return parent::getName();
}
}

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

@@ -1,21 +1,35 @@
<?php
require_once('DanbooruBridge.php');
class GelbooruBridge extends DanbooruBridge{
class GelbooruBridge extends DanbooruBridge {
const MAINTAINER = "mitsukarenai";
const NAME = "Gelbooru";
const URI = "http://gelbooru.com/";
const DESCRIPTION = "Returns images from given page";
const MAINTAINER = 'mitsukarenai';
const NAME = 'Gelbooru';
const URI = 'http://gelbooru.com/';
const DESCRIPTION = 'Returns images from given page';
const PATHTODATA='.thumb';
const IDATTRIBUTE='id';
const PATHTODATA = '.thumb';
const IDATTRIBUTE = 'id';
const TAGATTRIBUTE = 'title';
const PIDBYPAGE=63;
const PIDBYPAGE = 63;
protected function getFullURI(){
return $this->getURI().'index.php?page=post&s=list&'
.'&pid='.($this->getInput('p')?($this->getInput('p') -1)*static::PIDBYPAGE:'')
.'&tags='.urlencode($this->getInput('t'));
}
protected function getFullURI(){
return $this->getURI()
. 'index.php?page=post&s=list&pid='
. ($this->getInput('p') ? ($this->getInput('p') - 1) * static::PIDBYPAGE : '')
. '&tags=' . urlencode($this->getInput('t'));
}
protected function getTags($element){
$tags = parent::getTags($element);
$tags = explode(' ', $tags);
// Remove statistics from the tags list (identified by colon)
foreach($tags as $key => $tag) {
if(strpos($tag, ':') !== false) unset($tags[$key]);
}
return implode(' ', $tags);
}
}

View File

@@ -1,71 +1,76 @@
<?php
define('GIPHY_LIMIT', 10);
class GiphyBridge extends BridgeAbstract{
class GiphyBridge extends BridgeAbstract {
const MAINTAINER = "kraoc";
const NAME = "Giphy Bridge";
const URI = "http://giphy.com/";
const MAINTAINER = 'kraoc';
const NAME = 'Giphy Bridge';
const URI = 'http://giphy.com/';
const CACHE_TIMEOUT = 300; //5min
const DESCRIPTION = "Bridge for giphy.com";
const DESCRIPTION = 'Bridge for giphy.com';
const PARAMETERS = array( array(
's'=>array(
'name'=>'search tag',
'required'=>true
),
'n'=>array(
'name'=>'max number of returned items',
'type'=>'number'
)
));
const PARAMETERS = array( array(
's' => array(
'name' => 'search tag',
'required' => true
),
'n' => array(
'name' => 'max number of returned items',
'type' => 'number'
)
));
public function collectData(){
$html = '';
$base_url = 'http://giphy.com';
$html = getSimpleHTMLDOM(self::URI.'/search/'.urlencode($this->getInput('s').'/'))
or returnServerError('No results for this query.');
$base_url = 'http://giphy.com';
$html = getSimpleHTMLDOM(self::URI . '/search/' . urlencode($this->getInput('s') . '/'))
or returnServerError('No results for this query.');
$max = GIPHY_LIMIT;
if ($this->getInput('n')) {
$max = $this->getInput('n');
}
$max = GIPHY_LIMIT;
if($this->getInput('n')) {
$max = $this->getInput('n');
}
$limit = 0;
$kw = urlencode($this->getInput('s'));
foreach($html->find('div.hoverable-gif') as $entry) {
if($limit < $max) {
$node = $entry->first_child();
$href = $node->getAttribute('href');
$limit = 0;
$kw = urlencode($this->getInput('s'));
foreach($html->find('div.hoverable-gif') as $entry) {
if($limit < $max) {
$node = $entry->first_child();
$href = $node->getAttribute('href');
$html2 = getSimpleHTMLDOM(self::URI . $href)
or returnServerError('No results for this query.');
$figure = $html2->getElementByTagName('figure');
$img = $figure->firstChild();
$caption = $figure->lastChild();
$html2 = getSimpleHTMLDOM(self::URI . $href)
or returnServerError('No results for this query.');
$figure = $html2->getElementByTagName('figure');
$img = $figure->firstChild();
$caption = $figure->lastChild();
$item = array();
$item['id'] = $img->getAttribute('data-gif_id');
$item['uri'] = $img->getAttribute('data-bitly_gif_url');
$item['username'] = 'Giphy - '.ucfirst($kw);
$title = $caption->innertext();
$title = preg_replace('/\s+/', ' ',$title);
$title = str_replace('animated GIF', '', $title);
$title = str_replace($kw, '', $title);
$title = preg_replace('/\s+/', ' ',$title);
$title = trim($title);
if (strlen($title) <= 0) {
$title = $item['id'];
}
$item['title'] = trim($title);
$item['content'] =
'<a href="'.$item['uri'].'">'
.'<img src="'.$img->getAttribute('src').'" width="'.$img->getAttribute('data-original-width').'" height="'.$img->getAttribute('data-original-height').'" />'
.'</a>';
$item = array();
$item['id'] = $img->getAttribute('data-gif_id');
$item['uri'] = $img->getAttribute('data-bitly_gif_url');
$item['username'] = 'Giphy - ' . ucfirst($kw);
$title = $caption->innertext();
$title = preg_replace('/\s+/', ' ', $title);
$title = str_replace('animated GIF', '', $title);
$title = str_replace($kw, '', $title);
$title = preg_replace('/\s+/', ' ', $title);
$title = trim($title);
if(strlen($title) <= 0) {
$title = $item['id'];
}
$item['title'] = trim($title);
$item['content'] = '<a href="'
. $item['uri']
. '"><img src="'
. $img->getAttribute('src')
. '" width="'
. $img->getAttribute('data-original-width')
. '" height="'
. $img->getAttribute('data-original-height')
. '" /></a>';
$this->items[] = $item;
$limit++;
}
}
$this->items[] = $item;
$limit++;
}
}
}
}

View File

@@ -0,0 +1,163 @@
<?php
class GitHubGistBridge extends BridgeAbstract {
const NAME = 'GitHubGist comment bridge';
const URI = 'https://gist.github.com';
const DESCRIPTION = 'Generates feeds for Gist comments';
const MAINTAINER = 'logmanoriginal';
const CACHE_TIMEOUT = 3600;
const PARAMETERS = array(array(
'id' => array(
'name' => 'Gist',
'type' => 'text',
'required' => true,
'title' => 'Insert Gist ID or URI',
'exampleValue' => '2646763, https://gist.github.com/2646763'
)
));
private $filename;
public function getURI() {
$id = $this->getInput('id') ?: '';
$urlpath = parse_url($id, PHP_URL_PATH);
if($urlpath) {
$components = explode('/', $urlpath);
$id = end($components);
}
return static::URI . '/' . $id;
}
public function getName() {
return $this->filename ? $this->filename . ' - ' . static::NAME : static::NAME;
}
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI(),
null,
null,
true,
true,
DEFAULT_TARGET_CHARSET,
false, // Do NOT remove line breaks
DEFAULT_BR_TEXT,
DEFAULT_SPAN_TEXT)
or returnServerError('Could not request ' . $this->getURI());
$html = defaultLinkTo($html, $this->getURI());
$fileinfo = $html->find('[class="file-info"]', 0)
or returnServerError('Could not find file info!');
$this->filename = $fileinfo->plaintext;
$comments = $html->find('div[class="timeline-comment-wrapper"]');
if(is_null($comments)) { // no comments yet
return;
}
foreach($comments as $comment) {
$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)
or returnServerError('Could not find comment header text!');
$datetime = $comment->find('[datetime]', 0)
or returnServerError('Could not find comment datetime!');
$author = $comment->find('a.author', 0)
or returnServerError('Could not find author name!');
$message = $comment->find('[class="comment-body"]', 0)
or returnServerError('Could not find comment body!');
$item = array();
$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>';
$item['content'] = $this->fixContent($message);
// $item['enclosures'] = array();
// $item['categories'] = array();
$this->items[] = $item;
}
}
/** Removes all unnecessary tags and adds formatting */
private function fixContent($content){
// Restore code (inside <pre />) highlighting
foreach($content->find('pre') as $pre) {
$pre->style = <<<EOD
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f6f8fa;
border-radius: 3px;
word-wrap: normal;
box-sizing: border-box;
margin-bottom: 16px;
EOD;
$code = $pre->find('code', 0);
if($code) {
$code->style = <<<EOD
white-space: pre;
word-break: normal;
EOD;
}
}
// find <code /> not inside <pre /> (`inline-code`)
foreach($content->find('code') as $code) {
if($code->parent()->tag === 'pre') {
continue;
}
$code->style = <<<EOD
background-color: rgba(27,31,35,0.05);
padding: 0.2em 0.4em;
border-radius: 3px;
EOD;
}
// restore text spacing
foreach($content->find('p') as $p) {
$p->style = 'margin-bottom: 16px;';
}
// Remove unnecessary tags
$content = strip_tags(
$content->innertext,
'<p><a><img><ol><ul><li><table><tr><th><td><string><pre><code><br><hr><h>'
);
return $content;
}
}

View File

@@ -1,190 +1,219 @@
<?php
class GithubIssueBridge extends BridgeAbstract{
class GithubIssueBridge extends BridgeAbstract {
const MAINTAINER = 'Pierre Mazière';
const NAME = 'Github Issue';
const URI = 'https://github.com/';
const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = 'Returns the issues or comments of an issue of a github project';
const MAINTAINER = 'Pierre Mazière';
const NAME = 'Github Issue';
const URI = 'https://github.com/';
const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = 'Returns the issues or comments of an issue of a github project';
const PARAMETERS=array(
'global'=>array (
'u'=>array(
'name'=>'User name',
'required'=>true
),
'p'=>array(
'name'=>'Project name',
'required'=>true
)
),
const PARAMETERS = array(
'global' => array(
'u' => array(
'name' => 'User name',
'required' => true
),
'p' => array(
'name' => 'Project name',
'required' => true
)
),
'Project Issues' => array(
'c' => array(
'name' => 'Show Issues Comments',
'type' => 'checkbox'
)
),
'Issue comments' => array(
'i' => array(
'name' => 'Issue number',
'type' => 'number',
'required' => true
)
)
);
'Project Issues'=>array(
'c'=>array(
'name'=>'Show Issues Comments',
'type'=>'checkbox'
)
),
'Issue comments'=>array(
'i'=>array(
'name'=>'Issue number',
'type'=>'number',
'required'=>'true'
)
)
);
public function getName(){
$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 ';
}
$name = $prefix . $name;
break;
case 'Issue comments':
$name = static::NAME . ' ' . $name . ' #' . $this->getInput('i');
break;
default: return parent::getName();
}
return $name;
}
public function getName(){
$name=$this->getInput('u').'/'.$this->getInput('p');
switch($this->queriedContext){
case 'Project Issues':
if($this->getInput('c')){
$prefix=static::NAME.'s comments for ';
}else{
$prefix=static::NAME.'s for ';
}
$name=$prefix.$name;
break;
case 'Issue comments':
$name=static::NAME.' '.$name.' #'.$this->getInput('i');
break;
}
return $name;
}
public function getURI(){
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')) {
$uri .= '?q=is%3Aissue+sort%3Aupdated-desc';
}
return $uri;
}
public function getURI(){
$uri = static::URI.$this->getInput('u').'/'.$this->getInput('p').'/issues';
if($this->queriedContext==='Issue comments'){
$uri.='/'.$this->getInput('i');
}else if($this->getInput('c')){
$uri.='?q=is%3Aissue+sort%3Aupdated-desc';
}
return $uri;
}
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;
}
protected function extractIssueEvent($issueNbr, $title, $comment){
$comment = $comment->firstChild();
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p')
. '/issues/' . $issueNbr . '#' . $comment->getAttribute('id');
$author='unknown';
if($comment->find('.author',0)){
$author=$comment->find('.author',0)->plaintext;
}
$author = $comment->find('.author', 0)->plaintext;
$uri=static::URI.$this->getInput('u').'/'.$this->getInput('p').'/issues/'
.$issueNbr;
$title .= ' / ' . trim($comment->plaintext);
$comment=$comment->firstChild();
if(!$event){
$comment=$comment->nextSibling();
}
$content = $title;
if (null !== $comment->nextSibling()) {
$content = $comment->nextSibling()->innertext;
if ($comment->nextSibling()->nodeName() === 'span') {
$content = $comment->nextSibling()->nextSibling()->innertext;
}
}
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;
}
$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;
$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;
}
$item = array();
$item['author']=$author;
$item['uri']= $uri.'#'.$comment->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;
}
protected function extractIssueComment($issueNbr, $title, $comment){
$uri = static::URI . $this->getInput('u') . '/'
. $this->getInput('p') . '/issues/' . $issueNbr;
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));
$comments=$issue->find('.js-discussion',0);
foreach($comments->children() as $comment){
$classes=explode(' ',$comment->getAttribute('class'));
if(in_array('discussion-item',$classes) ||
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=array_merge($items,$item);
}
}
return $items;
}
$author = $comment->find('.author', 0)->plaintext;
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('No results for Github Issue '.$this->getURI());
$title .= ' / ' . trim(
$comment->find('.comment .timeline-comment-header-text', 0)->plaintext
);
switch($this->queriedContext){
case 'Issue comments':
$this->items=$this->extractIssueComments($html);
break;
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),' '));
$content = $comment->find('.comment-body', 0)->innertext;
$item=array();
$item['content']='';
$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;
}
if($this->getInput('c')){
$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));
continue;
}
$item['content']='Can not extract comments from '.$uri;
}
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)
);
$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('timeline-comment-wrapper', $classes)) {
$item = $this->extractIssueComment($issueNbr, $title, $comment);
$items[] = $item;
continue;
}
while (in_array('discussion-item', $classes)) {
$item = $this->extractIssueEvent($issueNbr, $title, $comment);
$items[] = $item;
$comment = $comment->nextSibling();
if (null == $comment) {
break;
}
$classes = explode(' ', $comment->getAttribute('class'));
}
}
return $items;
}
$item['author']=$info->find('a',0)->plaintext;
$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;
$item['content'].="\n".'Comments: '.($comments?$comments:'0');
$item['uri']=self::URI.$issue->find('.js-navigation-open',0)->getAttribute('href');
$this->items[]=$item;
}
break;
}
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError(
'No results for Github Issue ' . $this->getURI()
);
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="'.substr($item['uri'],0,strpos($item['uri'],'#')+1),
$item['content']
);
$item['title']=preg_replace('/\s+/',' ',$item['title']);
});
}
switch($this->queriedContext) {
case 'Issue comments':
$this->items = $this->extractIssueComments($html);
break;
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), ' ')
);
$item = array();
$item['content'] = '';
if($this->getInput('c')) {
$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)
);
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['title'] = html_entity_decode(
$issue->find('.js-navigation-open', 0)->plaintext,
ENT_QUOTES,
'UTF-8'
);
$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');
$this->items[] = $item;
}
break;
}
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="' . substr($item['uri'], 0, strpos($item['uri'], '#') + 1),
$item['content']
);
$item['title'] = preg_replace('/\s+/', ' ', $item['title']);
});
}
}

View File

@@ -0,0 +1,66 @@
<?php
class GithubSearchBridge extends BridgeAbstract {
const MAINTAINER = 'corenting';
const NAME = 'Github Repositories Search';
const URI = 'https://github.com/';
const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = 'Returns a specified repositories search (sorted by recently updated)';
const PARAMETERS = array( array(
's' => array(
'type' => 'text',
'name' => 'Search query'
)
));
public function collectData(){
$params = array('utf8' => '✓',
'q' => urlencode($this->getInput('s')),
's' => 'updated',
'o' => 'desc',
'type' => 'Repositories');
$url = self::URI . 'search?' . http_build_query($params);
$html = getSimpleHTMLDOM($url)
or returnServerError('Error while downloading the website content');
foreach($html->find('div.repo-list-item') as $element) {
$item = array();
$uri = $element->find('h3 a', 0)->href;
$uri = substr(self::URI, 0, -1) . $uri;
$item['uri'] = $uri;
$title = $element->find('h3', 0)->plaintext;
$item['title'] = $title;
// Description
if (count($element->find('p.d-inline-block')) != 0) {
$content = $element->find('p.d-inline-block', 0)->innertext;
} else{
$content = 'No description';
}
// 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);
$this->items[] = $item;
}
}
}

View File

@@ -1,22 +1,26 @@
<?php
class GizmodoBridge extends FeedExpander {
const MAINTAINER = "polopollo";
const NAME = "Gizmodo";
const URI = "http://gizmodo.com/";
const MAINTAINER = 'polopollo';
const NAME = 'Gizmodo';
const URI = 'http://gizmodo.com/';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = "Returns the newest posts from Gizmodo (full text).";
const DESCRIPTION = 'Returns the newest posts from Gizmodo (full text).';
protected function parseItem($item){
$item = parent::parseItem($item);
$articleHTMLContent = getSimpleHTMLDOMCached($item['uri']);
if(!$articleHTMLContent){
$text = 'Could not load '.$item['uri'];
}else{
if(!$articleHTMLContent) {
$text = 'Could not load ' . $item['uri'];
} else {
$text = $articleHTMLContent->find('div.entry-content', 0)->innertext;
foreach($articleHTMLContent->find('pagespeed_iframe') as $element) {
$text .= '<p>link to a iframe (could be a video): <a href="'.$element->src.'">'.$element->src.'</a></p><br>';
$text .= '<p>link to a iframe (could be a video): <a href="'
. $element->src
. '">'
. $element->src
. '</a></p><br>';
}
$text = strip_tags($text, '<p><b><a><blockquote><img><em>');

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

@@ -0,0 +1,61 @@
<?php
class GoComicsBridge extends BridgeAbstract {
const MAINTAINER = 'sky';
const NAME = 'GoComics Unofficial RSS';
const URI = 'https://www.gocomics.com/';
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'The Unofficial GoComics RSS';
const PARAMETERS = array( array(
'comicname' => array(
'name' => 'comicname',
'type' => 'text',
'required' => true
)
));
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request GoComics: ' . $this->getURI());
//Get info from first page
$author = preg_replace('/By /', '', $html->find('.media-subheading', 0)->plaintext);
$link = self::URI . $html->find('.gc-deck--cta-0', 0)->find('a', 0)->href;
for($i = 0; $i < 5; $i++) {
$item = array();
$page = getSimpleHTMLDOM($link)
or returnServerError('Could not request GoComics: ' . $link);
$imagelink = $page->find('.img-fluid', 1)->src;
$date = explode('/', $link);
$item['id'] = $imagelink;
$item['uri'] = $link;
$item['author'] = $author;
$item['title'] = 'GoComics ' . $this->getInput('comicname');
$item['timestamp'] = DateTime::createFromFormat('Ymd', $date[5] . $date[6] . $date[7])->getTimestamp();
$item['content'] = '<img src="' . $imagelink . '" />';
$link = self::URI . $page->find('.js-previous-comic', 0)->href;
$this->items[] = $item;
}
}
public function getURI(){
if(!is_null($this->getInput('comicname'))) {
return self::URI . urlencode($this->getInput('comicname'));
}
return parent::getURI();
}
public function getName(){
if(!is_null($this->getInput('comicname'))) {
return $this->getInput('comicname') . ' - GoComics';
}
return parent::getName();
}
}

View File

@@ -1,113 +1,208 @@
<?php
class GooglePlusPostBridge extends BridgeAbstract
{
protected $_title;
protected $_url;
class GooglePlusPostBridge extends BridgeAbstract{
const MAINTAINER = "Grummfy";
const NAME = "Google Plus Post Bridge";
const URI = "https://plus.google.com/";
private $title;
private $url;
const MAINTAINER = 'Grummfy, logmanoriginal';
const NAME = 'Google Plus Post Bridge';
const URI = 'https://plus.google.com';
const CACHE_TIMEOUT = 600; //10min
const DESCRIPTION = "Returns user public post (without API).";
const DESCRIPTION = 'Returns user public post (without API).';
const PARAMETERS = array( array(
'username'=>array(
'name'=>'username or Id',
'required'=>true
)
));
const PARAMETERS = array( array(
'username' => array(
'name' => 'username or Id',
'required' => true
),
'include_media' => array(
'name' => 'Include media',
'type' => 'checkbox',
'title' => 'Enable to include media in the feed content'
)
));
public function collectData()
{
// get content parsed
// $html = getSimpleHTMLDOM(__DIR__ . '/../posts2.html'
$html = getSimpleHTMLDOM(self::URI . urlencode($this->getInput('username')) . '/posts'
// force language
, false, stream_context_create(array('http'=> array(
'header' => 'Accept-Language: fr,fr-be,fr-fr;q=0.8,en;q=0.4,en-us;q=0.2;*' . "\r\n"
)))
) OR returnServerError('No results for this query.');
public function getIcon() {
return 'https://ssl.gstatic.com/images/branding/product/ico/google_plus_alldp.ico';
}
// get title, url, ... there is a lot of intresting stuff in meta
$this->_title = $html->find('meta[property]', 0)->getAttribute('content');
$this->_url = $html->find('meta[itemprop=url]', 0)->getAttribute('content');
public function collectData(){
// foreach ($html->find('meta') as $e)
// {
// $item = array();
// $item['content'] = var_export($e->attr, true);
// $this->items[] = $item;
// }
$username = $this->getInput('username');
// Usernames start with a + if it's not an ID
if(!is_numeric($username) && substr($username, 0, 1) !== '+') {
$username = '+' . $username;
}
$html = getSimpleHTMLDOM(static::URI . '/' . urlencode($username) . '/posts')
or returnServerError('No results for this query.');
$html = defaultLinkTo($html, static::URI);
$this->title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
$this->url = $html->find('meta[property=og:url]', 0)->getAttribute('content');
foreach($html->find('div[jsname=WsjYwc]') as $post) {
$item = array();
$item['author'] = $post->find('div div div div a', 0)->innertext;
$item['uri'] = $post->find('div div div a', 1)->href;
$timestamp = $post->find('a.qXj2He span', 0);
if($timestamp) {
$item['timestamp'] = strtotime('+' . preg_replace(
'/[^0-9A-Za-z]/',
'',
$timestamp->getAttribute('aria-label')));
}
$message = $post->find('div[jsname=EjRJtf]', 0);
// Empty messages are not supported right now
if(!$message) {
continue;
}
$item['content'] = '<div style="float: left; padding: 0 10px 10px 0;"><a href="'
. $this->url
. '"><img align="top" alt="'
. $item['author']
. '" src="'
. $post->find('div img', 0)->src
. '" /></a></div><div>'
. trim(strip_tags($message, '<a><p><div><img>'))
. '</div>';
// 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) ?: strlen($message->plaintext);
} else {
$end = strlen($message->plaintext);
}
if(strlen(substr($message->plaintext, 0, $end)) === strlen($message->plaintext)) {
$item['title'] = $message->plaintext;
} else {
$item['title'] = substr($message->plaintext, 0, $end) . '...';
}
$media = $post->find('[jsname="MTOxpb"]', 0);
if($media) {
$item['enclosures'] = array();
foreach($media->find('img') as $img) {
$item['enclosures'][] = $this->fixImage($img)->src;
}
if($this->getInput('include_media') === true && count($item['enclosures'] > 0)) {
$item['content'] .= '<div style="clear: both;"><a href="'
. $item['enclosures'][0]
. '"><img src="'
. $item['enclosures'][0]
. '" /></a></div>';
}
}
// Add custom parameters (only useful for JSON or Plaintext)
$item['fullname'] = $item['author'];
$item['avatar'] = $post->find('div img', 0)->src;
$item['id'] = $post->find('div div div', 0)->getAttribute('id');
$item['content_simple'] = $message->plaintext;
$this->items[] = $item;
// div[jsmodel=XNmfOc]
foreach($html->find('div.yt') as $post)
{
$item = array();
// $item['content'] = $post->find('div.Al', 0)->innertext;
$item['username'] = $item['fullname'] = $post->find('header.lea h3 a', 0)->innertext;
$item['id'] = $post->getAttribute('id');
// $item['title'] = $item['fullname'] = $post->find('header.lea', 0)->plaintext;
$item['avatar'] = $post->find('div.ys img', 0)->src;
// var_dump((($post->find('a.o-U-s', 0)->getAllAttributes())));
$item['uri'] = self::URI . $post->find('a.o-U-s', 0)->href;
$item['timestamp'] = strtotime($post->find('a.o-U-s', 0)->plaintext);
$this->items[] = $item;
// hashtag to treat : https://plus.google.com/explore/tag
$hashtags = array();
foreach($post->find('a.d-s') as $hashtag)
{
$hashtags[ trim($hashtag->plaintext) ] = self::URI . $hashtag->href;
}
$item['content'] = '';
// avatar display
$item['content'] .= '<div style="float:left; margin: 0 0.5em 0.5em 0;"><a href="' . self::URI . urlencode($this->getInput('username'));
$item['content'] .= '"><img align="top" alt="avatar" src="' . $item['avatar'].'" />' . $item['username'] . '</a></div>';
$content = $post->find('div.Al', 0);
// alter link
// $content = $content->innertext;
// $content = str_replace('href="./', 'href="' . self::URI, $content);
// $content = str_replace('href="photos', 'href="' . self::URI . 'photos', $content);
// XXX ugly but I don't have any idea how to do a better stuff, str_replace on link doesn't work as expected and ask too many checks
foreach($content->find('a') as $link)
{
$hasHttp = strpos($link->href, 'http');
$hasDoubleSlash = strpos($link->href, '//');
if ((!$hasHttp && !$hasDoubleSlash)
|| (false !== $hasHttp && strpos($link->href, 'http') != 0)
|| (false === $hasHttp && false !== $hasDoubleSlash && $hasDoubleSlash != 0))
{
// skipp bad link, for some hashtag or other stuff
if (strpos($link->href, '/') == 0)
{
$link->href = substr($link->href, 1);
}
$link->href = self::URI . $link->href;
}
}
$content = $content->innertext;
$item['content'] .= '<div style="margin-top: -1.5em">' . $content . '</div>';
// extract plaintext
$item['content_simple'] = $post->find('div.Al', 0)->plaintext;
}
// $html->save(__DIR__ . '/../posts2.html');
}
public function getName()
{
return $this->_title ?: 'Google Plus Post Bridge';
public function getName(){
return $this->title ?: 'Google Plus Post Bridge';
}
public function getURI()
public function getURI(){
return $this->url ?: parent::getURI();
}
private function fixImage($img) {
// There are certain images like .gif which link to a static picture and
// get replaced dynamically via JS in the browser. If we want the "real"
// image we need to account for that.
$urlparts = parse_url($img->src);
if(array_key_exists('host', $urlparts)) {
// For some reason some URIs don't contain the scheme, assume https
if(!array_key_exists('scheme', $urlparts)) {
$urlparts['scheme'] = 'https';
}
$pathelements = explode('/', $urlparts['path']);
switch($urlparts['host']) {
case 'lh3.googleusercontent.com':
if(pathinfo(end($pathelements), PATHINFO_EXTENSION)) {
// The second to last element of the path specifies the
// image format. The URL is still valid if we remove it.
unset($pathelements[count($pathelements) - 2]);
} elseif(strrpos(end($pathelements), '=') !== false) {
// Some images go throug a proxy. For those images they
// add size information after an equal sign.
// Example: '=w530-h298-n'. Again this can safely be
// removed to get the original image.
$pathelements[count($pathelements) - 1] = substr(
end($pathelements),
0,
strrpos(end($pathelements), '=')
);
}
break;
}
$urlparts['path'] = implode('/', $pathelements);
}
$img->src = $this->build_url($urlparts);
return $img;
}
/**
* From: https://gist.github.com/Ellrion/f51ba0d40ae1d62eeae44fd1adf7b704
* slightly adjusted to work with PHP < 7.0
* @param array $parts
* @return string
*/
private function build_url(array $parts)
{
return $this->_url ?: self::URI;
$scheme = isset($parts['scheme']) ? ($parts['scheme'] . '://') : '';
$host = isset($parts['host']) ? $parts['host'] : '';
$port = isset($parts['port']) ? (':' . $parts['port']) : '';
$user = isset($parts['user']) ? $parts['user'] : '';
$pass = isset($parts['pass']) ? (':' . $parts['pass']) : '';
$pass = ($user || $pass) ? ($pass . '@') : '';
$path = isset($parts['path']) ? $parts['path'] : '';
$query = isset($parts['query']) ? ('?' . $parts['query']) : '';
$fragment = isset($parts['fragment']) ? ('#' . $parts['fragment']) : '';
return implode('', [$scheme, $user, $pass, $host, $port, $path, $query, $fragment]);
}
}

View File

@@ -7,48 +7,58 @@
* qdr:y : in past year
* sbd:1 : sort by date (will only work if qdr: is specified)
*/
class GoogleSearchBridge extends BridgeAbstract{
class GoogleSearchBridge extends BridgeAbstract {
const MAINTAINER = "sebsauvage";
const NAME = "Google search";
const URI = "https://www.google.com/";
const MAINTAINER = 'sebsauvage';
const NAME = 'Google search';
const URI = 'https://www.google.com/';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = "Returns most recent results from Google search.";
const DESCRIPTION = 'Returns most recent results from Google search.';
const PARAMETERS = array( array(
'q'=>array(
'name'=>"keyword",
'required'=>true
)
));
const PARAMETERS = array(array(
'q' => array(
'name' => 'keyword',
'required' => true
)
));
public function collectData(){
$html = '';
public function collectData(){
$html = '';
$html = getSimpleHTMLDOM(self::URI
. 'search?q='
. urlencode($this->getInput('q'))
. '&num=100&complete=0&tbs=qdr:y,sbd:1')
or returnServerError('No results for this query.');
$html = getSimpleHTMLDOM(self::URI
.'search?q=' . urlencode($this->getInput('q'))
.'&num=100&complete=0&tbs=qdr:y,sbd:1')
or returnServerError('No results for this query.');
$emIsRes = $html->find('div[id=ires]', 0);
$emIsRes = $html->find('div[id=ires]',0);
if( !is_null($emIsRes) ){
foreach($emIsRes->find('li[class=g]') as $element) {
$item = array();
if(!is_null($emIsRes)) {
foreach($emIsRes->find('div[class=g]') as $element) {
// Extract direct URL from google href (eg. /url?q=...)
$t = $element->find('a[href]',0)->href;
$item['uri'] = ''.$t;
parse_str(parse_url($t, PHP_URL_QUERY),$parameters);
if (isset($parameters['q'])) { $item['uri'] = $parameters['q']; }
$item['title'] = $element->find('h3',0)->plaintext;
$item['content'] = $element->find('span[class=st]',0)->plaintext;
$this->items[] = $item;
}
}
}
$item = array();
public function getName(){
return $this->getInput('q') .' - Google search';
}
// Extract direct URL from google href (eg. /url?q=...)
$t = $element->find('a[href]', 0)->href;
$item['uri'] = '' . $t;
parse_str(parse_url($t, PHP_URL_QUERY), $parameters);
if(isset($parameters['q'])) {
$item['uri'] = $parameters['q'];
}
$item['title'] = $element->find('h3', 0)->plaintext;
$item['content'] = $element->find('span[class=st]', 0)->plaintext;
$this->items[] = $item;
}
}
}
public function getName(){
if(!is_null($this->getInput('q'))) {
return $this->getInput('q') . ' - Google search';
}
return parent::getName();
}
}

View File

@@ -0,0 +1,62 @@
<?php
class GrandComicsDatabaseBridge extends BridgeAbstract {
const MAINTAINER = 'corenting';
const NAME = 'Grand Comics Database Bridge';
const URI = 'https://www.comics.org/';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Returns the latest comics added to a series timeline';
const PARAMETERS = array( array(
'series' => array(
'name' => 'Series id (from the timeline URL)',
'required' => true,
'exampleValue' => '63051',
),
));
public function collectData(){
$url = self::URI . 'series/' . $this->getInput('series') . '/details/timeline/';
$html = getSimpleHTMLDOM($url)
or returnServerError('Error while downloading the website content');
$table = $html->find('table', 0);
$list = array_reverse($table->find('[class^=row_even]'));
$seriesName = $html->find('span[id=series_name]', 0)->innertext;
// Get row headers
$rowHeaders = $table->find('th');
foreach($list as $article) {
// Skip empty rows
$emptyRow = $article->find('td.empty_month');
if (count($emptyRow) != 0) {
continue;
}
$rows = $article->find('td');
$key_date = $rows[0]->innertext;
// Get URL too
$uri = 'https://www.comics.org' . $article->find('a')[0]->href;
// Build content
$content = '';
for($i = 0; $i < count($rowHeaders); $i++) {
$headerItem = $rowHeaders[$i]->innertext;
$rowItem = $rows[$i]->innertext;
$content = $content . $headerItem . ': ' . $rowItem . '<br/>';
}
// Build final item
$content = str_replace('href="/', 'href="' . static::URI, $content);
$item = array();
$item['title'] = $seriesName . ' - ' . $key_date;
$item['timestamp'] = strtotime($key_date);
$item['content'] = str_get_html($content);
$item['uri'] = $uri;
$this->items[] = $item;
}
}
}

View File

@@ -1,62 +1,83 @@
<?php
class HDWallpapersBridge extends BridgeAbstract {
const MAINTAINER = "nel50n";
const NAME = "HD Wallpapers Bridge";
const URI = "http://www.hdwallpapers.in/";
const MAINTAINER = 'nel50n';
const NAME = 'HD Wallpapers Bridge';
const URI = 'http://www.hdwallpapers.in/';
const CACHE_TIMEOUT = 43200; //12h
const DESCRIPTION = "Returns the latests wallpapers from HDWallpapers";
const DESCRIPTION = 'Returns the latests wallpapers from HDWallpapers';
const PARAMETERS = array( array(
'c'=>array(
'name'=>'category',
'defaultValue'=>'latest_wallpapers'
),
'm'=>array('name'=>'max number of wallpapers'),
'r'=>array(
'name'=>'resolution',
'defaultValue'=>'1920x1200',
'exampleValue'=>'1920x1200, 1680x1050,…'
)
));
const PARAMETERS = array( array(
'c' => array(
'name' => 'category',
'defaultValue' => 'latest_wallpapers'
),
'm' => array(
'name' => 'max number of wallpapers'
),
'r' => array(
'name' => 'resolution',
'defaultValue' => '1920x1200',
'exampleValue' => '1920x1200, 1680x1050,…'
)
));
public function collectData(){
$category = $this->category;
if (strrpos($category, 'wallpapers') !== strlen($category)-strlen('wallpapers')) {
$category .= '-desktop-wallpapers';
}
public function collectData(){
$category = $this->category;
if(strrpos($category, 'wallpapers') !== strlen($category) - strlen('wallpapers')) {
$category .= '-desktop-wallpapers';
}
$num = 0;
$max = $this->getInput('m') ?: 14;
$lastpage = 1;
$num = 0;
$max = $this->getInput('m') ?: 14;
$lastpage = 1;
for ($page = 1; $page <= $lastpage; $page++) {
$link = self::URI.'/'.$category.'/page/'.$page;
$html = getSimpleHTMLDOM($link) or returnServerError('No results for this query.');
for($page = 1; $page <= $lastpage; $page++) {
$link = self::URI . '/' . $category . '/page/' . $page;
$html = getSimpleHTMLDOM($link)
or returnServerError('No results for this query.');
if ($page === 1) {
preg_match('/page\/(\d+)$/', $html->find('.pagination a', -2)->href, $matches);
$lastpage = min($matches[1], ceil($max/14));
}
if($page === 1) {
preg_match('/page\/(\d+)$/', $html->find('.pagination a', -2)->href, $matches);
$lastpage = min($matches[1], ceil($max / 14));
}
foreach($html->find('.wallpapers .wall a') as $element) {
$thumbnail = $element->find('img', 0);
foreach($html->find('.wallpapers .wall a') as $element) {
$thumbnail = $element->find('img', 0);
$item = array();
// http://www.hdwallpapers.in/download/yosemite_reflections-1680x1050.jpg
$item['uri'] = self::URI.'/download'.str_replace('wallpapers.html', $this->getInput('r').'.jpg', $element->href);
$item['timestamp'] = time();
$item['title'] = $element->find('p', 0)->text();
$item['content'] = $item['title'].'<br><a href="'.$item['uri'].'"><img src="'.self::URI.$thumbnail->src.'" /></a>';
$this->items[] = $item;
$item = array();
// http://www.hdwallpapers.in/download/yosemite_reflections-1680x1050.jpg
$item['uri'] = self::URI
. '/download'
. str_replace('wallpapers.html', $this->getInput('r') . '.jpg', $element->href);
$num++;
if ($num >= $max)
break 2;
}
}
}
$item['timestamp'] = time();
$item['title'] = $element->find('p', 0)->text();
$item['content'] = $item['title']
. '<br><a href="'
. $item['uri']
. '"><img src="'
. self::URI
. $thumbnail->src
. '" /></a>';
public function getName(){
return 'HDWallpapers - '.str_replace(['__', '_'], [' & ', ' '], $this->getInput('c')).' ['.$this->getInput('r').']';
}
$this->items[] = $item;
$num++;
if ($num >= $max)
break 2;
}
}
}
public function getName(){
if(!is_null($this->getInput('c')) && !is_null($this->getInput('r'))) {
return 'HDWallpapers - '
. str_replace(['__', '_'], [' & ', ' '], $this->getInput('c'))
. ' ['
. $this->getInput('r')
. ']';
}
return parent::getName();
}
}

View File

@@ -1,23 +1,37 @@
<?php
class HentaiHavenBridge extends BridgeAbstract{
class HentaiHavenBridge extends BridgeAbstract {
const MAINTAINER = "albirew";
const NAME = "Hentai Haven";
const URI = "http://hentaihaven.org/";
const MAINTAINER = 'albirew';
const NAME = 'Hentai Haven';
const URI = 'http://hentaihaven.org/';
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = "Returns releases from Hentai Haven";
const DESCRIPTION = 'Returns releases from Hentai Haven';
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request Hentai Haven.');
foreach($html->find('div.zoe-grid') as $element) {
$item = array();
$item['uri'] = $element->find('div.brick-content h3 a', 0)->href;
$thumbnailUri = $element->find('a.thumbnail-image img', 0)->getAttribute('data-src');
$item['title'] = mb_convert_encoding(trim($element->find('div.brick-content h3 a', 0)->innertext), 'UTF-8', 'HTML-ENTITIES');
$item['tags'] = $element->find('div.oFlyout_bg div.oFlyout div.flyoutContent span.tags', 0)->plaintext;
$item['content'] = 'Tags: ' . $item['tags'].'<br><br><a href="' . $item['uri'] . '"><img width="300" height="169" src="' . $thumbnailUri . '" /></a><br>' . $element->find('div.oFlyout_bg div.oFlyout div.flyoutContent p.description', 0)->innertext;
$this->items[] = $item;
}
}
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request Hentai Haven.');
foreach($html->find('div.zoe-grid') as $element) {
$item = array();
$item['uri'] = $element->find('div.brick-content h3 a', 0)->href;
$thumbnailUri = $element->find('a.thumbnail-image img', 0)->getAttribute('data-src');
$item['title'] = mb_convert_encoding(
trim($element->find('div.brick-content h3 a', 0)->innertext),
'UTF-8',
'HTML-ENTITIES'
);
$item['tags'] = $element->find('div.oFlyout_bg div.oFlyout div.flyoutContent span.tags', 0)->plaintext;
$item['content'] = 'Tags: '
. $item['tags']
. '<br><br><a href="'
. $item['uri']
. '"><img width="300" height="169" src="'
. $thumbnailUri
. '" /></a><br>'
. $element->find('div.oFlyout_bg div.oFlyout div.flyoutContent p.description', 0)->innertext;
$this->items[] = $item;
}
}
}

1395
bridges/HotUKDealsBridge.php Normal file

File diff suppressed because it is too large Load Diff

310
bridges/IPBBridge.php Normal file
View File

@@ -0,0 +1,310 @@
<?php
class IPBBridge extends FeedExpander {
const NAME = 'IPB Bridge';
const URI = 'https://www.invisionpower.com';
const DESCRIPTION = 'Returns feeds for forums powered by IPB';
const MAINTAINER = 'logmanoriginal';
const PARAMETERS = array(
array(
'uri' => array(
'name' => 'URI',
'type' => 'text',
'required' => true,
'title' => 'Insert forum, subforum or topic URI',
'exampleValue' => 'https://invisioncommunity.com/forums/forum/499-feedback-and-ideas/'
),
'limit' => array(
'name' => 'Limit',
'type' => 'number',
'required' => false,
'title' => 'Specifies the number of items to return on each request (-1: all)',
'defaultValue' => 10
)
)
);
const CACHE_TIMEOUT = 3600;
// Constants for internal use
const FORUM_TYPE_LIST_FILTER = '.cForumTopicTable';
const FORUM_TYPE_TABLE_FILTER = '#forum_table';
const TOPIC_TYPE_ARTICLE = 'article';
const TOPIC_TYPE_DIV = 'div.post_block';
public function getURI(){
return $this->getInput('uri') ?: parent::getURI();
}
public function collectData(){
// The URI cannot be the mainpage (or anything related)
switch(parse_url($this->getInput('uri'), PHP_URL_PATH)) {
case null:
case '/index.php':
returnClientError('Provided URI is invalid!');
break;
default:
break;
}
// Sanitize the URI (because else it won't work)
$uri = rtrim($this->getInput('uri'), '/'); // No trailing slashes!
// Forums might provide feeds, though that's optional *facepalm*
// Let's check if there is a valid feed available
$headers = get_headers($uri . '.xml');
if($headers[0] === 'HTTP/1.1 200 OK') { // Heureka! It's a valid feed!
return $this->collectExpandableDatas($uri);
}
// No valid feed, so do it the hard way
$html = getSimpleHTMLDOM($uri)
or returnServerError('Could not request ' . $this->getInput('uri') . '!');
$limit = $this->getInput('limit');
// Determine if this is a topic or a forum
switch(true) {
case $this->isTopic($html):
$this->collectTopic($html, $limit);
break;
case $this->isForum($html);
$this->collectForum($html);
break;
default:
returnClientError('Unknown type!');
break;
}
}
private function isForum($html){
return !is_null($html->find('div[data-controller*=forums.front.forum.forumPage]', 0))
|| !is_null($html->find(static::FORUM_TYPE_TABLE_FILTER, 0));
}
private function isTopic($html){
return !is_null($html->find('div[data-controller*=core.front.core.commentFeed]', 0))
|| !is_null($html->find(static::TOPIC_TYPE_DIV, 0));
}
private function collectForum($html){
// There are multiple forum designs in use (depends on version?)
// 1 - Uses an ordered list (based on https://invisioncommunity.com/forums)
// 2 - Uses a table (based on https://onehallyu.com)
switch(true) {
case !is_null($html->find(static::FORUM_TYPE_LIST_FILTER, 0)):
$this->collectForumList($html);
break;
case !is_null($html->find(static::FORUM_TYPE_TABLE_FILTER, 0)):
$this->collectForumTable($html);
break;
default:
returnClientError('Unknown forum format!');
break;
}
}
private function collectForumList($html){
foreach($html->find(static::FORUM_TYPE_LIST_FILTER, 0)->children() as $row) {
// Columns: Title, Statistics, Last modified
$item = array();
$item['uri'] = $row->find('a', 0)->href;
$item['title'] = $row->find('a', 0)->title;
$item['author'] = $row->find('a', 1)->innertext;
$item['timestamp'] = strtotime($row->find('time', 0)->getAttribute('datetime'));
$this->items[] = $item;
}
}
private function collectForumTable($html){
foreach($html->find(static::FORUM_TYPE_TABLE_FILTER, 0)->children() as $row) {
// Columns: Icon, Content, Preview, Statistics, Last modified
$item = array();
// Skip header row
if(!is_null($row->find('th', 0))) continue;
$item['uri'] = $row->find('a', 0)->href;
$item['title'] = $row->find('.title', 0)->plaintext;
$item['timestamp'] = strtotime($row->find('[itemprop=dateCreated]', 0)->plaintext);
$this->items[] = $item;
}
}
private function collectTopic($html, $limit){
// There are multiple topic designs in use (depends on version?)
// 1 - Uses articles (based on https://invisioncommunity.com/forums)
// 2 - Uses divs (based on https://onehallyu.com)
switch(true) {
case !is_null($html->find(static::TOPIC_TYPE_ARTICLE, 0)):
$this->collectTopicHistory($html, $limit, 'collectTopicArticle');
break;
case !is_null($html->find(static::TOPIC_TYPE_DIV, 0)):
$this->collectTopicHistory($html, $limit, 'collectTopicDiv');
break;
default:
returnClientError('Unknown topic format!');
break;
}
}
private function collectTopicHistory($html, $limit, $callback){
// Make sure the callback is valid!
if(!method_exists($this, $callback))
returnServerError('Unknown function (\'' . $callback . '\')!');
$next = null; // Holds the URI of the next page
while(true) {
$next = $this->$callback($html, is_null($next));
if(is_null($next) || ($limit > 0 && count($this->items) >= $limit)) {
break;
}
$html = getSimpleHTMLDOMCached($next);
}
// We might have more items than specified, remove excess
$this->items = array_slice($this->items, 0, $limit);
}
private function collectTopicArticle($html, $firstrun = true){
$title = $html->find('h1.ipsType_pageTitle', 0)->plaintext;
// Are we on last page?
if($firstrun && !is_null($html->find('.ipsPagination', 0))) {
$last = $html->find('.ipsPagination_last a', 0)->{'data-page'};
$active = $html->find('.ipsPagination_active a', 0)->{'data-page'};
if($active !== $last) {
// Load last page into memory (cached)
$html = getSimpleHTMLDOMCached($html->find('.ipsPagination_last a', 0)->href);
}
}
foreach(array_reverse($html->find(static::TOPIC_TYPE_ARTICLE)) as $article) {
$item = array();
$item['uri'] = $article->find('time', 0)->parent()->href;
$item['author'] = $article->find('aside a', 0)->plaintext;
$item['title'] = $item['author'] . ' - ' . $title;
$item['timestamp'] = strtotime($article->find('time', 0)->getAttribute('datetime'));
$content = $article->find('[data-role=commentContent]', 0);
$content = $this->scaleImages($content);
$item['content'] = $this->fixContent($content);
$item['enclosures'] = $this->findImages($article->find('[data-role=commentContent]', 0)) ?: null;
$this->items[] = $item;
}
// Return whatever page comes next (previous, as we add in inverse order)
// Do we have a previous page? (inactive means no)
if(!is_null($html->find('li[class=ipsPagination_prev ipsPagination_inactive]', 0))) {
return null; // No, or no more
} elseif(!is_null($html->find('li[class=ipsPagination_prev]', 0))) {
return $html->find('.ipsPagination_prev a', 0)->href;
}
return null;
}
private function collectTopicDiv($html, $firstrun = true){
$title = $html->find('h1.ipsType_pagetitle', 0)->plaintext;
// Are we on last page?
if($firstrun && !is_null($html->find('.pagination', 0))) {
$active = $html->find('li[class=page active]', 0)->plaintext;
// There are two ways the 'last' page is displayed:
// - With a distict 'last' button (only if there are enough pages)
// - With a button for each page (use last button)
if(!is_null($html->find('li.last', 0))) {
$last = $html->find('li.last a', 0);
} else {
$last = $html->find('li[class=page] a', -1);
}
if($active !== $last->plaintext) {
// Load last page into memory (cached)
$html = getSimpleHTMLDOMCached($last->href);
}
}
foreach(array_reverse($html->find(static::TOPIC_TYPE_DIV)) as $article) {
$item = array();
$item['uri'] = $article->find('a[rel=bookmark]', 0)->href;
$item['author'] = $article->find('.author', 0)->plaintext;
$item['title'] = $item['author'] . ' - ' . $title;
$item['timestamp'] = strtotime($article->find('.published', 0)->getAttribute('title'));
$content = $article->find('[itemprop=commentText]', 0);
$content = $this->scaleImages($content);
$item['content'] = $this->fixContent($content);
$item['enclosures'] = $this->findImages($article->find('.post_body', 0)) ?: null;
$this->items[] = $item;
}
// Return whatever page comes next (previous, as we add in inverse order)
// Do we have a previous page?
if(!is_null($html->find('li.prev', 0))) {
return $html->find('li.prev a', 0)->href;
}
return null;
}
/** Returns all images from the provide HTML DOM */
private function findImages($html){
$images = array();
foreach($html->find('img') as $img) {
$images[] = $img->src;
}
return $images;
}
/** Sets the maximum width and height for all images */
private function scaleImages($html, $width = 400, $height = 400){
foreach($html->find('img') as $img) {
$img->style = "max-width: {$width}px; max-height: {$height}px;";
}
return $html;
}
/** Removes all unnecessary tags and adds formatting */
private function fixContent($html){
// Restore quote highlighting
foreach($html->find('blockquote') as $quote) {
$quote->style = <<<EOD
padding: 0px 15px;
border-width: 1px 1px 1px 2px;
border-style: solid;
border-color: #ededed #e8e8e8 #dbdbdb #666666;
background: #fbfbfb;
EOD;
}
// Remove unnecessary tags
$content = strip_tags(
$html->innertext,
'<p><a><img><ol><ul><li><table><tr><th><td><strong><blockquote><br><hr><h>'
);
return $content;
}
}

View File

@@ -1,38 +1,52 @@
<?php
class IdenticaBridge extends BridgeAbstract{
class IdenticaBridge extends BridgeAbstract {
const MAINTAINER = "mitsukarenai";
const NAME = "Identica Bridge";
const URI = "https://identi.ca/";
const MAINTAINER = 'mitsukarenai';
const NAME = 'Identica Bridge';
const URI = 'https://identi.ca/';
const CACHE_TIMEOUT = 300; // 5min
const DESCRIPTION = "Returns user timelines";
const DESCRIPTION = 'Returns user timelines';
const PARAMETERS = array( array(
'u'=>array(
'name'=>'username',
'required'=>true
)
));
const PARAMETERS = array( array(
'u' => array(
'name' => 'username',
'required' => true
)
));
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Requested username can\'t be found.');
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Requested username can\'t be found.');
foreach($html->find('li.major') as $dent) {
$item = array();
$item['uri'] = html_entity_decode($dent->find('a', 0)->href); // get dent link
$item['timestamp'] = strtotime($dent->find('abbr.easydate', 0)->plaintext); // extract dent timestamp
$item['content'] = trim($dent->find('div.activity-content', 0)->innertext); // extract dent text
$item['title'] = $this->getInput('u') . ' | ' . $item['content'];
$this->items[] = $item;
}
}
foreach($html->find('li.major') as $dent) {
$item = array();
public function getName(){
return $this->getInput('u') .' - Identica Bridge';
}
// get dent link
$item['uri'] = html_entity_decode($dent->find('a', 0)->href);
public function getURI(){
return self::URI.urlencode($this->getInput('u'));
}
// extract dent timestamp
$item['timestamp'] = strtotime($dent->find('abbr.easydate', 0)->plaintext);
// extract dent text
$item['content'] = trim($dent->find('div.activity-content', 0)->innertext);
$item['title'] = $this->getInput('u') . ' | ' . $item['content'];
$this->items[] = $item;
}
}
public function getName(){
if(!is_null($this->getInput('u'))) {
return $this->getInput('u') . ' - Identica Bridge';
}
return parent::getName();
}
public function getURI(){
if(!is_null($this->getInput('u'))) {
return self::URI . urlencode($this->getInput('u'));
}
return parent::getURI();
}
}

View File

@@ -1,71 +1,175 @@
<?php
class InstagramBridge extends BridgeAbstract{
class InstagramBridge extends BridgeAbstract {
const MAINTAINER = "pauder";
const NAME = "Instagram Bridge";
const URI = "http://instagram.com/";
const DESCRIPTION = "Returns the newest images";
const MAINTAINER = 'pauder';
const NAME = 'Instagram Bridge';
const URI = 'https://www.instagram.com/';
const DESCRIPTION = 'Returns the newest images';
const PARAMETERS = array( array(
'u'=>array(
'name'=>'username',
'required'=>true
)
));
const PARAMETERS = array(
'Username' => array(
'u' => array(
'name' => 'username',
'required' => true
)
),
'Hashtag' => array(
'h' => array(
'name' => 'hashtag',
'required' => true
)
),
'Location' => array(
'l' => array(
'name' => 'location',
'required' => true
)
),
'global' => array(
'media_type' => array(
'name' => 'Media type',
'type' => 'list',
'required' => false,
'values' => array(
'All' => 'all',
'Story' => 'story',
'Video' => 'video',
'Picture' => 'picture',
),
'defaultValue' => 'all'
)
)
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request Instagram.');
);
$innertext = null;
public function collectData(){
foreach($html->find('script') as $script)
{
if ('' === $script->innertext) {
continue;
}
if(is_null($this->getInput('u')) && $this->getInput('media_type') == 'story') {
returnClientError('Stories are not supported for hashtags nor locations!');
}
$pos = strpos(trim($script->innertext), 'window._sharedData');
if (0 !== $pos)
{
continue;
}
$data = $this->getInstagramJSON($this->getURI());
$innertext = $script->innertext;
break;
}
if(!is_null($this->getInput('u'))) {
$userMedia = $data->entry_data->ProfilePage[0]->graphql->user->edge_owner_to_timeline_media->edges;
} 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;
}
$json = trim(substr($innertext, $pos+18), ' =;');
$data = json_decode($json);
foreach($userMedia as $media) {
$media = $media->node;
if(!is_null($this->getInput('u'))) {
switch($this->getInput('media_type')) {
case 'all': break;
case 'video':
if($media->__typename != 'GraphVideo') continue 2;
break;
case 'picture':
if($media->__typename != 'GraphImage') continue 2;
break;
case 'story':
if($media->__typename != 'GraphSidecar') continue 2;
break;
default: break;
}
} else {
if($this->getInput('media_type') == 'video' && !$media->is_video) continue;
}
$item = array();
$item['uri'] = self::URI . 'p/' . $media->shortcode . '/';
$userMedia = $data->entry_data->ProfilePage[0]->user->media->nodes;
if (isset($media->owner->username)) {
$item['author'] = $media->owner->username;
}
foreach($userMedia as $media)
{
if (isset($media->edge_media_to_caption->edges[0]->node->text)) {
$textContent = $media->edge_media_to_caption->edges[0]->node->text;
} else {
$textContent = basename($media->display_url);
}
$item = array();
$item['uri'] = self::URI.'p/'.$media->code.'/';
$item['content'] = '<img src="' . htmlentities($media->display_src) . '" />';
if (isset($media->caption))
{
$item['title'] = $media->caption;
} else {
$item['title'] = basename($media->display_src);
}
$item['timestamp'] = $media->date;
$this->items[] = $item;
$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') {
$data = $this->getInstagramStory($item['uri']);
$item['content'] = $data[0];
$item['enclosures'] = $data[1];
} else {
$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);
}
public function getName(){
return $this->getInput('u') .' - Instagram Bridge';
}
$item['timestamp'] = $media->taken_at_timestamp;
public function getURI(){
return self::URI.urlencode($this->getInput('u'));
}
$this->items[] = $item;
}
}
protected function getInstagramStory($uri) {
$data = $this->getInstagramJSON($uri);
$mediaInfo = $data->entry_data->PostPage[0]->graphql->shortcode_media;
//Process the first element, that isn't in the node graph
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 . '" />';
foreach($mediaInfo->edge_sidecar_to_children->edges as $media) {
$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];
}
protected function getInstagramJSON($uri) {
$html = getContents($uri)
or returnServerError('Could not request Instagram.');
$scriptRegex = '/window\._sharedData = (.*);<\/script>/';
preg_match($scriptRegex, $html, $matches, PREG_OFFSET_CAPTURE, 0);
return json_decode($matches[1][0]);
}
public function getName(){
if(!is_null($this->getInput('u'))) {
return $this->getInput('u') . ' - Instagram Bridge';
}
return parent::getName();
}
public function getURI(){
if(!is_null($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

@@ -0,0 +1,370 @@
<?php
/**
* This class implements a bridge for http://www.instructables.com, supporting
* general feeds and feeds by category. Instructables doesn't support HTTPS as
* of now (23.06.2018), so all connections are insecure!
*
* Remarks:
* - For some reason it is very important to have the category URI end with a
* slash, otherwise the site defaults to the main category (i.e. Technology)!
* If you need to update the categories list, enable the 'listCategories'
* function (see comments below) and run the bridge with format=Html (see page
* source)
*/
class InstructablesBridge extends BridgeAbstract {
const NAME = 'Instructables Bridge';
const URI = 'http://www.instructables.com';
const DESCRIPTION = 'Returns general feeds and feeds by category';
const MAINTAINER = 'logmanoriginal';
const PARAMETERS = array(
'Category' => array(
'category' => array(
'name' => 'Category',
'type' => 'list',
'required' => true,
'values' => array(
'Play' => array(
'All' => '/play/',
'KNEX' => '/play/knex/',
'Offbeat' => '/play/offbeat/',
'Lego' => '/play/lego/',
'Airsoft' => '/play/airsoft/',
'Card Games' => '/play/card-games/',
'Guitars' => '/play/guitars/',
'Instruments' => '/play/instruments/',
'Magic Tricks' => '/play/magic-tricks/',
'Minecraft' => '/play/minecraft/',
'Music' => '/play/music/',
'Nerf' => '/play/nerf/',
'Nintendo' => '/play/nintendo/',
'Office Supplies' => '/play/office-supplies/',
'Paintball' => '/play/paintball/',
'Paper Airplanes' => '/play/paper-airplanes/',
'Party Tricks' => '/play/party-tricks/',
'PlayStation' => '/play/playstation/',
'Pranks and Humor' => '/play/pranks-and-humor/',
'Puzzles' => '/play/puzzles/',
'Siege Engines' => '/play/siege-engines/',
'Sports' => '/play/sports/',
'Table Top' => '/play/table-top/',
'Toys' => '/play/toys/',
'Video Games' => '/play/video-games/',
'Wii' => '/play/wii/',
'Xbox' => '/play/xbox/',
'Yo-Yo' => '/play/yo-yo/',
),
'Craft' => array(
'All' => '/craft/',
'Art' => '/craft/art/',
'Sewing' => '/craft/sewing/',
'Paper' => '/craft/paper/',
'Jewelry' => '/craft/jewelry/',
'Fashion' => '/craft/fashion/',
'Books & Journals' => '/craft/books-and-journals/',
'Cards' => '/craft/cards/',
'Clay' => '/craft/clay/',
'Duct Tape' => '/craft/duct-tape/',
'Embroidery' => '/craft/embroidery/',
'Felt' => '/craft/felt/',
'Fiber Arts' => '/craft/fiber-arts/',
'Gifts & Wrapping' => '/craft/gifts-and-wrapping/',
'Knitting & Crocheting' => '/craft/knitting-and-crocheting/',
'Leather' => '/craft/leather/',
'Mason Jars' => '/craft/mason-jars/',
'No-Sew' => '/craft/no-sew/',
'Parties & Weddings' => '/craft/parties-and-weddings/',
'Print Making' => '/craft/print-making/',
'Soap' => '/craft/soap/',
'Wallets' => '/craft/wallets/',
),
'Technology' => array(
'All' => '/technology/',
'Electronics' => '/technology/electronics/',
'Arduino' => '/technology/arduino/',
'Photography' => '/technology/photography/',
'Leds' => '/technology/leds/',
'Science' => '/technology/science/',
'Reuse' => '/technology/reuse/',
'Apple' => '/technology/apple/',
'Computers' => '/technology/computers/',
'3D Printing' => '/technology/3D-Printing/',
'Robots' => '/technology/robots/',
'Art' => '/technology/art/',
'Assistive Tech' => '/technology/assistive-technology/',
'Audio' => '/technology/audio/',
'Clocks' => '/technology/clocks/',
'CNC' => '/technology/cnc/',
'Digital Graphics' => '/technology/digital-graphics/',
'Gadgets' => '/technology/gadgets/',
'Kits' => '/technology/kits/',
'Laptops' => '/technology/laptops/',
'Lasers' => '/technology/lasers/',
'Linux' => '/technology/linux/',
'Microcontrollers' => '/technology/microcontrollers/',
'Microsoft' => '/technology/microsoft/',
'Mobile' => '/technology/mobile/',
'Raspberry Pi' => '/technology/raspberry-pi/',
'Remote Control' => '/technology/remote-control/',
'Sensors' => '/technology/sensors/',
'Software' => '/technology/software/',
'Soldering' => '/technology/soldering/',
'Speakers' => '/technology/speakers/',
'Steampunk' => '/technology/steampunk/',
'Tools' => '/technology/tools/',
'USB' => '/technology/usb/',
'Wearables' => '/technology/wearables/',
'Websites' => '/technology/websites/',
'Wireless' => '/technology/wireless/',
),
'Workshop' => array(
'All' => '/workshop/',
'Woodworking' => '/workshop/woodworking/',
'Tools' => '/workshop/tools/',
'Gardening' => '/workshop/gardening/',
'Cars' => '/workshop/cars/',
'Metalworking' => '/workshop/metalworking/',
'Cardboard' => '/workshop/cardboard/',
'Electric Vehicles' => '/workshop/electric-vehicles/',
'Energy' => '/workshop/energy/',
'Furniture' => '/workshop/furniture/',
'Home Improvement' => '/workshop/home-improvement/',
'Home Theater' => '/workshop/home-theater/',
'Hydroponics' => '/workshop/hydroponics/',
'Laser Cutting' => '/workshop/laser-cutting/',
'Lighting' => '/workshop/lighting/',
'Molds & Casting' => '/workshop/molds-and-casting/',
'Motorcycles' => '/workshop/motorcycles/',
'Organizing' => '/workshop/organizing/',
'Pallets' => '/workshop/pallets/',
'Repair' => '/workshop/repair/',
'Shelves' => '/workshop/shelves/',
'Solar' => '/workshop/solar/',
'Workbenches' => '/workshop/workbenches/',
),
'Home' => array(
'All' => '/home/',
'Halloween' => '/home/halloween/',
'Decorating' => '/home/decorating/',
'Organizing' => '/home/organizing/',
'Pets' => '/home/pets/',
'Life Hacks' => '/home/life-hacks/',
'Beauty' => '/home/beauty/',
'Christmas' => '/home/christmas/',
'Cleaning' => '/home/cleaning/',
'Education' => '/home/education/',
'Finances' => '/home/finances/',
'Gardening' => '/home/gardening/',
'Green' => '/home/green/',
'Health' => '/home/health/',
'Hiding Places' => '/home/hiding-places/',
'Holidays' => '/home/holidays/',
'Homesteading' => '/home/homesteading/',
'Kids' => '/home/kids/',
'Kitchen' => '/home/kitchen/',
'Life Skills' => '/home/life-skills/',
'Parenting' => '/home/parenting/',
'Pest Control' => '/home/pest-control/',
'Relationships' => '/home/relationships/',
'Reuse' => '/home/reuse/',
'Travel' => '/home/travel/',
),
'Outside' => array(
'All' => '/outside/',
'Bikes' => '/outside/bikes/',
'Survival' => '/outside/survival/',
'Backyard' => '/outside/backyard/',
'Beach' => '/outside/beach/',
'Birding' => '/outside/birding/',
'Boats' => '/outside/boats/',
'Camping' => '/outside/camping/',
'Climbing' => '/outside/climbing/',
'Fire' => '/outside/fire/',
'Fishing' => '/outside/fishing/',
'Hunting' => '/outside/hunting/',
'Kites' => '/outside/kites/',
'Knives' => '/outside/knives/',
'Knots' => '/outside/knots/',
'Paracord' => '/outside/paracord/',
'Rockets' => '/outside/rockets/',
'Skateboarding' => '/outside/skateboarding/',
'Snow' => '/outside/snow/',
'Water' => '/outside/water/',
),
'Food' => array(
'All' => '/food/',
'Dessert' => '/food/dessert/',
'Snacks & Appetizers' => '/food/snacks-and-appetizers/',
'Bacon' => '/food/bacon/',
'BBQ & Grilling' => '/food/bbq-and-grilling/',
'Beverages' => '/food/beverages/',
'Bread' => '/food/bread/',
'Breakfast' => '/food/breakfast/',
'Cake' => '/food/cake/',
'Candy' => '/food/candy/',
'Canning & Preserves' => '/food/canning-and-preserves/',
'Cocktails & Mocktails' => '/food/cocktails-and-mocktails/',
'Coffee' => '/food/coffee/',
'Cookies' => '/food/cookies/',
'Cupcakes' => '/food/cupcakes/',
'Homebrew' => '/food/homebrew/',
'Main Course' => '/food/main-course/',
'Pasta' => '/food/pasta/',
'Pie' => '/food/pie/',
'Pizza' => '/food/pizza/',
'Salad' => '/food/salad/',
'Sandwiches' => '/food/sandwiches/',
'Soups & Stews' => '/food/soups-and-stews/',
'Vegetarian & Vegan' => '/food/vegetarian-and-vegan/',
),
'Costumes' => array(
'All' => '/costumes/',
'Props' => '/costumes/props-and-accessories/',
'Animals' => '/costumes/animals/',
'Comics' => '/costumes/comics/',
'Fantasy' => '/costumes/fantasy/',
'For Kids' => '/costumes/for-kids/',
'For Pets' => '/costumes/for-pets/',
'Funny' => '/costumes/funny/',
'Games' => '/costumes/games/',
'Historic & Futuristic' => '/costumes/historic-and-futuristic/',
'Makeup' => '/costumes/makeup/',
'Masks' => '/costumes/masks/',
'Scary' => '/costumes/scary/',
'TV & Movies' => '/costumes/tv-and-movies/',
'Weapons & Armor' => '/costumes/weapons-and-armor/',
)
),
'title' => 'Select your category (required)',
'defaultValue' => 'Technology'
),
'filter' => array(
'name' => 'Filter',
'type' => 'list',
'required' => true,
'values' => array(
'Featured' => ' ',
'Recent' => 'recent/',
'Popular' => 'popular/',
'Views' => 'views/',
'Contest Winners' => 'winners/'
),
'title' => 'Select a filter',
'defaultValue' => 'Featured'
)
)
);
private $uri;
public function collectData() {
// Enable the following line to get the category list (dev mode)
// $this->listCategories();
$this->uri = static::URI;
switch($this->queriedContext) {
case 'Category': $this->uri .= $this->getInput('category') . $this->getInput('filter');
}
$html = getSimpleHTMLDOM($this->uri)
or returnServerError('Error loading category ' . $this->uri);
foreach($html->find('ul.explore-covers-list li') as $cover) {
$item = array();
$item['uri'] = static::URI . $cover->find('a.cover-image', 0)->href;
$item['title'] = $cover->find('.title', 0)->innertext;
$item['author'] = $this->getCategoryAuthor($cover);
$item['content'] = '<a href='
. $item['uri']
. '><img src='
. $cover->find('a.cover-image img', 0)->src
. '></a>';
$image = str_replace('.RECTANGLE1', '.LARGE', $cover->find('a.cover-image img', 0)->src);
$item['enclosures'] = [$image];
$this->items[] = $item;
}
}
public function getName() {
if(!is_null($this->getInput('category'))
&& !is_null($this->getInput('filter'))) {
foreach(self::PARAMETERS[$this->queriedContext]['category']['values'] as $key => $value) {
$subcategory = array_search($this->getInput('category'), $value);
if($subcategory !== false)
break;
}
$filter = array_search(
$this->getInput('filter'),
self::PARAMETERS[$this->queriedContext]['filter']['values']
);
return $subcategory . ' (' . $filter . ') - ' . static::NAME;
}
return parent::getName();
}
public function getURI() {
if(!is_null($this->getInput('category'))
&& !is_null($this->getInput('filter'))) {
return $this->uri;
}
return parent::getURI();
}
/**
* Returns a list of categories for development purposes (used to build the
* parameters list)
*/
private function listCategories(){
// Use arbitrary category to receive full list
$html = getSimpleHTMLDOM(self::URI . '/technology/');
foreach($html->find('.channel a') as $channel) {
$name = html_entity_decode(trim($channel->innertext));
// Remove unwanted entities
$name = str_replace("'", '', $name);
$name = str_replace('&#39;', '', $name);
$uri = $channel->href;
$category = explode('/', $uri)[1];
if(!isset($categories)
|| !array_key_exists($category, $categories)
|| !in_array($uri, $categories[$category]))
$categories[$category][$name] = $uri;
}
// Build PHP array manually
foreach($categories as $key => $value) {
$name = ucfirst($key);
echo "'{$name}' => array(\n";
echo "\t'All' => '/{$key}/',\n";
foreach($value as $name => $uri) {
echo "\t'{$name}' => '{$uri}',\n";
}
echo "),\n";
}
die;
}
/**
* Returns the author as anchor for a given cover.
*/
private function getCategoryAuthor($cover) {
return '<a href='
. static::URI . $cover->find('span.author a', 0)->href
. '>'
. $cover->find('span.author a', 0)->innertext
. '</a>';
}
}

View File

@@ -1,468 +0,0 @@
<?php
class IsoHuntBridge extends BridgeAbstract{
const MAINTAINER = 'logmanoriginal';
const NAME = 'isoHunt Bridge';
const URI = 'https://isohunt.to/';
const CACHE_TIMEOUT = 300; //5min
const DESCRIPTION = 'Returns the latest results by category or search result';
const PARAMETERS = array(
/*
* Get feeds for one of the "latest" categories
* Notice: The categories "News" and "Top Searches" are received from the main page
* Elements are sorted by name ascending!
*/
'By "Latest" category' => array(
'latest_category'=>array(
'name'=>'Latest category',
'type'=>'list',
'required'=>true,
'title'=>'Select your category',
'defaultValue'=>'news',
'values'=>array(
'Hot Torrents'=>'hot_torrents',
'News'=>'news',
'Releases'=>'releases',
'Torrents'=>'torrents'
)
)
),
/*
* Get feeds for one of the "torrent" categories
* Make sure to add new categories also to get_torrent_category_index($)!
* Elements are sorted by name ascending!
*/
'By "Torrent" category' => array(
'torrent_category'=>array(
'name'=>'Torrent category',
'type'=>'list',
'required'=>true,
'title'=>'Select your category',
'defaultValue'=>'anime',
'values'=>array(
'Adult'=>'adult',
'Anime'=>'anime',
'Books'=>'books',
'Games'=>'games',
'Movies'=>'movies',
'Music'=>'music',
'Other'=>'other',
'Series & TV'=>'series_tv',
'Software'=>'software'
)
),
'torrent_popularity'=>array(
'name'=>'Sort by popularity',
'type'=>'checkbox',
'title'=>'Activate to receive results by popularity'
)
),
/*
* Get feeds for a specific search request
*/
'Search torrent by name' => array(
'search_name'=>array(
'name'=>'Name',
'required'=>true,
'title'=>'Insert your search query',
'exampleValue'=>'Bridge'
),
'search_category'=>array(
'name'=>'Category',
'type'=>'list',
'title'=>'Select your category',
'defaultValue'=>'all',
'values'=>array(
'Adult'=>'adult',
'All'=>'all',
'Anime'=>'anime',
'Books'=>'books',
'Games'=>'games',
'Movies'=>'movies',
'Music'=>'music',
'Other'=>'other',
'Series & TV'=>'series_tv',
'Software'=>'software'
)
)
)
);
public function getURI(){
$uri=self::URI;
switch($this->queriedContext){
case 'By "Latest" category':
switch($this->getInput('latest_category')){
case 'hot_torrents':
$uri .= 'statistic/hot/torrents';
break;
case 'news':
break;
case 'releases':
$uri .= 'releases.php';
break;
case 'torrents':
$uri .= 'latest.php';
break;
}
break;
case 'By "Torrent" category':
$uri .= $this->build_category_uri(
$this->getInput('torrent_category'),
$this->getInput('torrent_popularity')
);
break;
case 'Search torrent by name':
$category=$this->getInput('search_category');
$uri .= $this->build_category_uri($category);
if($category!=='movies')
$uri .= '&ihq=' . urlencode($this->getInput('search_name'));
break;
}
return $uri;
}
public function getName(){
switch($this->queriedContext){
case 'By "Latest" category':
$categoryName =
array_search(
$this->getInput('latest_category'),
self::PARAMETERS['By "Latest" category']['latest_category']['values']
);
$name = 'Latest '.$categoryName.' - ' . self::NAME;
break;
case 'By "Torrent" category':
$categoryName =
array_search(
$this->getInput('torrent_category'),
self::PARAMETERS['By "Torrent" category']['torrent_category']['values']
);
$name = 'Category: ' . $categoryName . ' - ' . self::NAME;
break;
case 'Search torrent by name':
$categoryName =
array_search(
$this->getInput('search_category'),
self::PARAMETERS['Search torrent by name']['search_category']['values']
);
$name = 'Search: "' . $this->getInput('search_name') . '" in category: ' . $categoryName . ' - ' . self::NAME;
break;
}
return $name;
}
public function collectData(){
$html = $this->load_html($this->getURI());
switch($this->queriedContext){
case 'By "Latest" category':
switch($this->getInput('latest_category')){
case 'hot_torrents':
$this->get_latest_hot_torrents($html);
break;
case 'news':
$this->get_latest_news($html);
break;
case 'releases':
case 'torrents':
$this->get_latest_torrents($html);
break;
}
break;
case 'By "Torrent" category':
if($this->getInput('torrent_category') === 'movies'){
// This one is special (content wise)
$this->get_movie_torrents($html);
}else{
$this->get_latest_torrents($html);
}
break;
case 'Search torrent by name':
if( $this->getInput('search_category') === 'movies'){
// This one is special (content wise)
$this->get_movie_torrents($html);
} else {
$this->get_latest_torrents($html);
}
break;
}
}
#region Helper functions for "Movie Torrents"
private function get_movie_torrents($html){
$container = $html->find('div#w0', 0);
if(!$container)
returnServerError('Unable to find torrent container!');
$torrents = $container->find('article');
if(!$torrents)
returnServerError('Unable to find torrents!');
foreach($torrents as $torrent){
$anchor = $torrent->find('a', 0);
if(!$anchor)
returnServerError('Unable to find anchor!');
$date = $torrent->find('small', 0);
if(!$date)
returnServerError('Unable to find date!');
$item = array();
$item['uri'] = $this->fix_relative_uri($anchor->href);
$item['title'] = $anchor->title;
// $item['author'] =
$item['timestamp'] = strtotime($date->plaintext);
$item['content'] = $this->fix_relative_uri($torrent->innertext);
$this->items[] = $item;
}
}
#endregion
#region Helper functions for "Latest Hot Torrents"
private function get_latest_hot_torrents($html){
$container = $html->find('div#serps', 0);
if(!$container)
returnServerError('Unable to find torrent container!');
$torrents = $container->find('tr');
if(!$torrents)
returnServerError('Unable to find torrents!');
// Remove first element (header row)
$torrents = array_slice($torrents, 1);
foreach($torrents as $torrent){
$cell = $torrent->find('td', 0);
if(!$cell)
returnServerError('Unable to find cell!');
$element = $cell->find('a', 0);
if(!$element)
returnServerError('Unable to find element!');
$item = array();
$item['uri'] = $element->href;
$item['title'] = $element->plaintext;
// $item['author'] =
// $item['timestamp'] =
// $item['content'] =
$this->items[] = $item;
}
}
#endregion
#region Helper functions for "Latest News"
private function get_latest_news($html){
$container = $html->find('div#postcontainer', 0);
if(!$container)
returnServerError('Unable to find post container!');
$posts = $container->find('div.index-post');
if(!$posts)
returnServerError('Unable to find posts!');
foreach($posts as $post){
$item = array();
$item['uri'] = $this->latest_news_extract_uri($post);
$item['title'] = $this->latest_news_extract_title($post);
$item['author'] = $this->latest_news_extract_author($post);
$item['timestamp'] = $this->latest_news_extract_timestamp($post);
$item['content'] = $this->latest_news_extract_content($post);
$this->items[] = $item;
}
}
private function latest_news_extract_author($post){
$author = $post->find('small', 0);
if(!$author)
returnServerError('Unable to find author!');
// The author is hidden within a string like: 'Posted by {author} on {date}'
preg_match('/Posted\sby\s(.*)\son/i', $author->innertext, $matches);
return $matches[1];
}
private function latest_news_extract_timestamp($post){
$date = $post->find('small', 0);
if(!$date)
returnServerError('Unable to find date!');
// The date is hidden within a string like: 'Posted by {author} on {date}'
preg_match('/Posted\sby\s.*\son\s(.*)/i', $date->innertext, $matches);
$timestamp = strtotime($matches[1]);
// Make sure date is not in the future (dates are given like 'Nov. 20' without year)
if($timestamp > time()){
$timestamp = strtotime('-1 year', $timestamp);
}
return $timestamp;
}
private function latest_news_extract_title($post){
$title = $post->find('a', 0);
if(!$title)
returnServerError('Unable to find title!');
return $title->plaintext;
}
private function latest_news_extract_uri($post){
$uri = $post->find('a', 0);
if(!$uri)
returnServerError('Unable to find uri!');
return $uri->href;
}
private function latest_news_extract_content($post){
$content = $post->find('div', 0);
if(!$content)
returnServerError('Unable to find content!');
// Remove <h2>...</h2> (title)
foreach($content->find('h2') as $element){
$element->outertext = '';
}
// Remove <small>...</small> (author)
foreach($content->find('small') as $element){
$element->outertext = '';
}
return $content->innertext;
}
#endregion
#region Helper functions for "Latest Torrents", "Latest Releases" and "Torrent Category"
private function get_latest_torrents($html){
$container = $html->find('div#serps', 0);
if(!$container)
returnServerError('Unable to find torrent container!');
$torrents = $container->find('tr[data-key]');
if(!$torrents)
returnServerError('Unable to find torrents!');
foreach($torrents as $torrent){
$item = array();
$item['uri'] = $this->latest_torrents_extract_uri($torrent);
$item['title'] = $this->latest_torrents_extract_title($torrent);
$item['author'] = $this->latest_torrents_extract_author($torrent);
$item['timestamp'] = $this->latest_torrents_extract_timestamp($torrent);
$item['content'] = ''; // There is no valuable content
$this->items[] = $item;
}
}
private function latest_torrents_extract_title($torrent){
$cell = $torrent->find('td.title-row', 0);
if(!$cell)
returnServerError('Unable to find title cell!');
$title = $cell->find('span', 0);
if(!$title)
returnServerError('Unable to find title!');
return $title->plaintext;
}
private function latest_torrents_extract_uri($torrent){
$cell = $torrent->find('td.title-row', 0);
if(!$cell)
returnServerError('Unable to find title cell!');
$uri = $cell->find('a', 0);
if(!$uri)
returnServerError('Unable to find uri!');
return $this->fix_relative_uri($uri->href);
}
private function latest_torrents_extract_author($torrent){
$cell = $torrent->find('td.user-row', 0);
if(!$cell)
return; // No author
$user = $cell->find('a', 0);
if(!$user)
returnServerError('Unable to find user!');
return $user->plaintext;
}
private function latest_torrents_extract_timestamp($torrent){
$cell = $torrent->find('td.date-row', 0);
if(!$cell)
returnServerError('Unable to find date cell!');
return strtotime('-' . $cell->plaintext, time());
}
#endregion
#region Generic helper functions
private function load_html($uri){
$html = getSimpleHTMLDOM($uri);
if(!$html)
returnServerError('Unable to load ' . $uri . '!');
return $html;
}
private function fix_relative_uri($uri){
return preg_replace('/\//i', self::URI, $uri, 1);
}
private function build_category_uri($category, $order_popularity = false){
switch($category){
case 'anime': $index = 1; break;
case 'software' : $index = 2; break;
case 'games' : $index = 3; break;
case 'adult' : $index = 4; break;
case 'movies' : $index = 5; break;
case 'music' : $index = 6; break;
case 'other' : $index = 7; break;
case 'series_tv' : $index = 8; break;
case 'books': $index = 9; break;
case 'all':
default: $index = 0; break;
}
return 'torrents/?iht=' . $index . '&ihs=' . ($order_popularity ? 1 : 0) . '&age=0';
}
#endregion
}

View File

@@ -1,89 +1,106 @@
<?php
class JapanExpoBridge extends BridgeAbstract {
const MAINTAINER = 'Ginko';
const NAME = 'Japan Expo Actualités';
const URI = 'http://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(
'mode'=>array(
'name'=>'Show full contents',
'type'=>'checkbox',
)
));
const MAINTAINER = 'Ginko';
const NAME = 'Japan Expo Actualités';
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(
'mode' => array(
'name' => 'Show full contents',
'type' => 'checkbox',
)
));
public function collectData(){
public function getIcon() {
return 'https://s.japan-expo.com/katana/images/JES073/favicons/paris.png';
}
function french_pubdate_to_timestamp($date_to_parse) {
return strtotime(
strtr(
strtolower(str_replace('Publié le ', '', $date_to_parse)),
array(
'janvier' => 'jan',
'février' => 'feb',
'mars' => 'march',
'avril' => 'apr',
'mai' => 'may',
'juin' => 'jun',
'juillet' => 'jul',
'août' => 'aug',
'septembre' => 'sep',
'octobre' => 'oct',
'novembre' => 'nov',
'décembre' => 'dec'
)
)
);
}
public function collectData(){
$convert_article_images = function ($matches) {
if (is_array($matches) && count($matches) > 1) {
return '<img src="'.$matches[1].'" />';
}
};
function frenchPubDateToTimestamp($date_to_parse) {
return strtotime(
strtr(
strtolower(str_replace('Publié le ', '', $date_to_parse)),
array(
'janvier' => 'jan',
'février' => 'feb',
'mars' => 'march',
'avril' => 'apr',
'mai' => 'may',
'juin' => 'jun',
'juillet' => 'jul',
'août' => 'aug',
'septembre' => 'sep',
'octobre' => 'oct',
'novembre' => 'nov',
'décembre' => 'dec'
)
)
);
}
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request JapanExpo: '.self::URI);
$fullcontent = $this->getInput('mode');
$count = 0;
$convert_article_images = function($matches){
if(is_array($matches) && count($matches) > 1) {
return '<img src="' . $matches[1] . '" />';
}
};
foreach ($html->find('a._tile2') as $element) {
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request JapanExpo: ' . self::URI);
$fullcontent = $this->getInput('mode');
$count = 0;
$url = $element->href;
$thumbnail = 'http://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)
$thumbnail = trim($img_search_result[1], "'");
foreach($html->find('a._tile2') as $element) {
if ($fullcontent) {
if ($count >= 5) {
break;
}
$url = $element->href;
$thumbnail = 'https://s.japan-expo.com/katana/images/JES049/paris.png';
preg_match('/url\(([^)]+)\)/', $element->find('img.rspvimgset', 0)->style, $img_search_result);
$article_html = getSimpleHTMLDOMCached('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();
$title = $title_html->plaintext;
$headings = $title_html->next_sibling()->outertext;
$article = $article_html->find('div.content', 0)->innertext;
$article = preg_replace_callback('/<img [^>]+ style="[^\(]+\(\'([^\']+)\'[^>]+>/i', $convert_article_images, $article);
$content = $headings.$article;
} else {
$date_text = $element->find('span.date', 0)->plaintext;
$timestamp = french_pubdate_to_timestamp($date_text);
$title = trim($element->find('span._title', 0)->plaintext);
$content = '<img src="'.$thumbnail.'"></img><br />'.$date_text.'<br /><a href="'.$url.'">Lire l\'article</a>';
}
if(count($img_search_result) >= 2)
$thumbnail = trim($img_search_result[1], "'");
$item = array();
$item['uri'] = $url;
$item['title'] = $title;
$item['timestamp'] = $timestamp;
$item['content'] = $content;
$this->items[] = $item;
$count++;
}
}
if($fullcontent) {
if($count >= 5) {
break;
}
$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();
$title = $title_html->plaintext;
$headings = $title_html->next_sibling()->outertext;
$article = $article_html->find('div.content', 0)->innertext;
$article = preg_replace_callback(
'/<img [^>]+ style="[^\(]+\(\'([^\']+)\'[^>]+>/i',
$convert_article_images,
$article);
$content = $headings . $article;
} else {
$date_text = $element->find('span.date', 0)->plaintext;
$timestamp = frenchPubDateToTimestamp($date_text);
$title = trim($element->find('span._title', 0)->plaintext);
$content = '<img src="'
. $thumbnail
. '"></img><br />'
. $date_text
. '<br /><a href="'
. $url
. '">Lire l\'article</a>';
}
$item = array();
$item['uri'] = $url;
$item['title'] = $title;
$item['timestamp'] = $timestamp;
$item['enclosures'] = array($thumbnail);
$item['content'] = $content;
$this->items[] = $item;
$count++;
}
}
}

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