mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-15 13:04:01 +02:00
Compare commits
245 Commits
2018-03-11
...
revert-909
Author | SHA1 | Date | |
---|---|---|---|
|
db25a68072 | ||
|
8c97953211 | ||
|
d987ceec73 | ||
|
392e3ff6c7 | ||
|
e295dc5a79 | ||
|
b9f6bc8197 | ||
|
9c1c0f2974 | ||
|
65da157fff | ||
|
5fe943562a | ||
|
c58331f74d | ||
|
145a46ae1d | ||
|
1a7a7bad98 | ||
|
27d6a22675 | ||
|
b55ec51e0e | ||
|
07b4c72d5d | ||
|
2e6cbd1ce7 | ||
|
2ac2f3dc66 | ||
|
e2dfea2b77 | ||
|
c4896c7791 | ||
|
7621784598 | ||
|
1cfe939927 | ||
|
c56f7abc2a | ||
|
e3030cbbfd | ||
|
953c6e1022 | ||
|
dbd44f64dd | ||
|
89ca42da54 | ||
|
b4b5340b7e | ||
|
a508dddb36 | ||
|
cb488d9d8c | ||
|
9820ad5c0f | ||
|
ea2d54523d | ||
|
87d218296e | ||
|
afd5ef0f1d | ||
|
30bc5179c2 | ||
|
7596be65f2 | ||
|
16f0ee7104 | ||
|
e0323f06cd | ||
|
717b0bdd9c | ||
|
62d737efe2 | ||
|
6fce03daa7 | ||
|
7561c0685d | ||
|
f48eac854f | ||
|
a87e7781b1 | ||
|
0dc761d6cf | ||
|
d14f8e3c83 | ||
|
b4aea21f71 | ||
|
c06a09fe99 | ||
|
704ad50607 | ||
|
d89c65d219 | ||
|
9a3c776096 | ||
|
85e8a67568 | ||
|
ee158468fa | ||
|
5779f641c0 | ||
|
b90bcee1fc | ||
|
996295e82f | ||
|
13bd7fe21b | ||
|
fcc9f9fd61 | ||
|
e1c4914b1c | ||
|
93e7ea9fea | ||
|
2d1b446bd1 | ||
|
1d451610d6 | ||
|
f853ffc07c | ||
|
e3a5a6a170 | ||
|
243e324efc | ||
|
ae58b1566e | ||
|
c044694b21 | ||
|
db24f55c86 | ||
|
eb30038d6b | ||
|
712a581ed6 | ||
|
d3df4b51b8 | ||
|
e6476a600d | ||
|
811e8d8c88 | ||
|
adc6f72e97 | ||
|
182153485c | ||
|
bf9946d1fc | ||
|
ec60752650 | ||
|
6688cf0c3b | ||
|
ae45a8cfee | ||
|
e34ef6cb4f | ||
|
5c92a736fa | ||
|
911bcfb246 | ||
|
efa550ef61 | ||
|
d5d7683ed3 | ||
|
fe94914eb5 | ||
|
622802e5d4 | ||
|
6da8daf1a3 | ||
|
654e502e84 | ||
|
c8ace9e3bd | ||
|
5722a6c139 | ||
|
458b826871 | ||
|
b397a42876 | ||
|
111c45d010 | ||
|
55b36b0455 | ||
|
de8cee6a1c | ||
|
123fce4394 | ||
|
a3f99c9c3f | ||
|
bf30ad127c | ||
|
37f84196b7 | ||
|
44764f7182 | ||
|
19f294d71d | ||
|
b0e33e4e01 | ||
|
558fa50a2a | ||
|
ffb8b82c73 | ||
|
422c125d8e | ||
|
059656c370 | ||
|
9fc1e97efe | ||
|
be3620acb7 | ||
|
16c0a61232 | ||
|
704a87ad97 | ||
|
c4cccfe0f3 | ||
|
d07deb0930 | ||
|
e7dab5d351 | ||
|
ad82d50bbd | ||
|
c305c1ded7 | ||
|
f14a5bd771 | ||
|
a20d5f9af0 | ||
|
ee28b124e0 | ||
|
7dee3a175a | ||
|
5fea9fc1f5 | ||
|
6bceb2b2db | ||
|
df81fa62d1 | ||
|
f8c6400373 | ||
|
de7622ebbf | ||
|
09c9d015b4 | ||
|
3a496e3b18 | ||
|
df58f5bbdb | ||
|
9d0452d11b | ||
|
f92ac49947 | ||
|
a574fa15ac | ||
|
8f9a385b4d | ||
|
53bdfa3bf0 | ||
|
53278b2eed | ||
|
5f3c55b808 | ||
|
fb79a67370 | ||
|
3c4e12ceba | ||
|
0d1923c52f | ||
|
ce896b4247 | ||
|
a4b2d88dbe | ||
|
65ec04ea98 | ||
|
afb4de318b | ||
|
43bb17f995 | ||
|
bae7a5879f | ||
|
bd760cbcee | ||
|
cd20b4476f | ||
|
d83f2f285b | ||
|
15e6d77569 | ||
|
f97d2ef254 | ||
|
91ae2a23d7 | ||
|
066ef1d7db | ||
|
4facbf32e3 | ||
|
6bd76af326 | ||
|
caa622ffec | ||
|
c4d489f018 | ||
|
6a98293fb3 | ||
|
d79630e3b8 | ||
|
1f2fe25471 | ||
|
87fc9e9156 | ||
|
c7b0c9fd31 | ||
|
fbf874cb29 | ||
|
049ee52fb5 | ||
|
3f41d0593a | ||
|
7126f5e838 | ||
|
ead7b2e8de | ||
|
0d80a19e84 | ||
|
42c699f474 | ||
|
2bc8daa101 | ||
|
bca79d3f88 | ||
|
90dc968fd1 | ||
|
da6b98851c | ||
|
71c29d4192 | ||
|
193ca87afa | ||
|
5ea79ac1fc | ||
|
937ea49271 | ||
|
95686b803c | ||
|
5087f5f79e | ||
|
4a5f190e0e | ||
|
01a2746715 | ||
|
f4a60c1777 | ||
|
1b08bce779 | ||
|
9fa74a36c6 | ||
|
7493e2b5b8 | ||
|
8e468a9ca7 | ||
|
50924b9213 | ||
|
4c5013bc82 | ||
|
7dc09db9ca | ||
|
d92da8f0f7 | ||
|
064ba456e8 | ||
|
8ac8e08abf | ||
|
c4f32c31a8 | ||
|
4369e077c2 | ||
|
1045850043 | ||
|
2d8f4dc3c5 | ||
|
779b638fb4 | ||
|
3ca59392c2 | ||
|
9b34b68180 | ||
|
79ebdc4b39 | ||
|
8770c87389 | ||
|
c1e3352218 | ||
|
00570ce1b4 | ||
|
df33dcff4e | ||
|
e60b5ab193 | ||
|
b0c7a62f74 | ||
|
57b15a089e | ||
|
4b7fbe4188 | ||
|
2390fb58b3 | ||
|
1e8d29f6ec | ||
|
644d13686c | ||
|
aa0ff1c9b1 | ||
|
539d9f1f06 | ||
|
5ece801ce7 | ||
|
4dcea6d9c9 | ||
|
d69e2521f1 | ||
|
7927d73719 | ||
|
0620f30ae0 | ||
|
795494cfce | ||
|
ba8542156c | ||
|
55f112e034 | ||
|
208fff801d | ||
|
3c9860de43 | ||
|
a16ec196c5 | ||
|
887fc7b037 | ||
|
1bd4a40f71 | ||
|
494169f959 | ||
|
178177e787 | ||
|
1cb83ccea3 | ||
|
c899399569 | ||
|
0f93370e92 | ||
|
45c3dcb636 | ||
|
ecfc220b10 | ||
|
4b3efed7ec | ||
|
bc28c5da8e | ||
|
5bd9c1611d | ||
|
6caca4946b | ||
|
ee78e7613f | ||
|
2df2623430 | ||
|
de5f850cdb | ||
|
ac6847045c | ||
|
df6da837dc | ||
|
41b7984a4e | ||
|
38c7e0272e | ||
|
29c690dbcd | ||
|
8ba817478b | ||
|
cacbe90102 | ||
|
cb91cd5d2f | ||
|
52dfa3fe76 |
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
||||
.git
|
||||
cache/*
|
||||
DEBUG
|
||||
Dockerfile
|
||||
whitelist.txt
|
||||
phpcs.xml
|
||||
CHANGELOG.md
|
||||
CONTRIBUTING.md
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -227,8 +227,12 @@ pip-log.txt
|
||||
/cache
|
||||
/whitelist.txt
|
||||
DEBUG
|
||||
config.ini.php
|
||||
|
||||
######################
|
||||
## VisualStudioCode ##
|
||||
######################
|
||||
.vscode/*
|
||||
.vscode/*
|
||||
|
||||
#Builder
|
||||
.buildconfig
|
||||
|
20
.travis.yml
20
.travis.yml
@@ -3,12 +3,26 @@ sudo: false
|
||||
language: php
|
||||
|
||||
install:
|
||||
- pear channel-update pear.php.net
|
||||
- pear install PHP_CodeSniffer
|
||||
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
|
||||
composer global require squizlabs/PHP_CodeSniffer;
|
||||
else
|
||||
pear channel-update pear.php.net;
|
||||
pear install PHP_CodeSniffer;
|
||||
fi
|
||||
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
|
||||
composer global require phpunit/phpunit ^6;
|
||||
fi
|
||||
|
||||
script:
|
||||
- phpenv rehash
|
||||
- phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p
|
||||
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
|
||||
/home/travis/.composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
||||
else
|
||||
phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
||||
fi
|
||||
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
|
||||
phpunit --configuration=phpunit.xml --include-path=lib/;
|
||||
fi
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
@@ -1,47 +1,47 @@
|
||||
### Pull request policy
|
||||
Fix one issue per pull request.
|
||||
Squash commits before opening a pull request.
|
||||
Respect the coding style policy.
|
||||
Name your PR like the following :
|
||||
|
||||
* When correcting a single bridge, use `[BridgeName] Feature`.
|
||||
* When fixing a problem in a specific file, use `[FileName] Feature`.
|
||||
* When fixing a general problem, use `category : feature`.
|
||||
* [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 should pass the unit tests before they can be merged.
|
||||
Note that all pull-requests must pass all tests before they can be merged.
|
||||
|
||||
### Coding style
|
||||
|
||||
Use `camelCase` for variables and methods.
|
||||
Use `UPPERCASE` for constants.
|
||||
Use `PascalCase` for class names. When creating a bridge, your class and PHP file should be named `MyImplementationBridge`.
|
||||
Use tabs for indentation.
|
||||
Add an empty line at the end of your file.
|
||||
|
||||
Use `''` to encapsulate strings, including in arrays.
|
||||
Prefer lines shorter than 80 chars, no line longer than 120 chars.
|
||||
PHP constants should be in lower case (`true, false, null`...)
|
||||
|
||||
|
||||
* Add spaces between the logical operator and your expressions (not needed for the `!` operator).
|
||||
* Use `||` and `&&` instead of `or` and `and`.
|
||||
* Add space between your condition and the opening bracket/closing bracket.
|
||||
* Don't put a space between `if` and your bracket.
|
||||
* Use `elseif` instead of `else if`.
|
||||
* Add new lines in your conditions if they are containing more than one line.
|
||||
* Example :
|
||||
|
||||
```PHP
|
||||
if($a == true && $b) {
|
||||
print($a);
|
||||
} else if(!$b) {
|
||||
|
||||
$a = !$a;
|
||||
$b = $b >> $a;
|
||||
print($b);
|
||||
|
||||
} else {
|
||||
print($b);
|
||||
}
|
||||
```
|
||||
|
||||
* [Whitespace](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace)
|
||||
* [Add a new line at the end of a file](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#add-a-new-line-at-the-end-of-a-file)
|
||||
* [Do not add a whitespace before a semicolon](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#add-a-new-line-at-the-end-of-a-file)
|
||||
* [Do not add whitespace at start or end of a file or end of a line](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#do-not-add-whitespace-at-start-or-end-of-a-file-or-end-of-a-line)
|
||||
* [Indentation](https://github.com/RSS-Bridge/rss-bridge/wiki/Indentation)
|
||||
* [Use tabs for indentation](https://github.com/RSS-Bridge/rss-bridge/wiki/Indentation#use-tabs-for-indentation)
|
||||
* [Maximum line length](https://github.com/RSS-Bridge/rss-bridge/wiki/Maximum-line-length)
|
||||
* [The maximum line length should not exceed 80 characters](https://github.com/RSS-Bridge/rss-bridge/wiki/Maximum-line-length#the-maximum-line-length-should-not-exceed-80-characters)
|
||||
* [Strings](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings)
|
||||
* [Whenever possible use single quoted strings](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#whenever-possible-use-single-quote-strings)
|
||||
* [Add spaces around the concatenation operator](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#add-spaces-around-the-concatenation-operator)
|
||||
* [Use a single string instead of concatenating](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#use-a-single-string-instead-of-concatenating)
|
||||
* [Constants](https://github.com/RSS-Bridge/rss-bridge/wiki/Constants)
|
||||
* [Use UPPERCASE for constants](https://github.com/RSS-Bridge/rss-bridge/wiki/Constants#use-uppercase-for-constants)
|
||||
* [Keywords](https://github.com/RSS-Bridge/rss-bridge/wiki/Keywords)
|
||||
* [Use lowercase for `true`, `false` and `null`](https://github.com/RSS-Bridge/rss-bridge/wiki/Keywords#use-lowercase-for-true-false-and-null)
|
||||
* [Operators](https://github.com/RSS-Bridge/rss-bridge/wiki/Operators)
|
||||
* [Operators must have a space around them](https://github.com/RSS-Bridge/rss-bridge/wiki/Operators#operators-must-have-a-space-around-them)
|
||||
* [Functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions)
|
||||
* [Parameters with default values must appear last in functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#parameters-with-default-values-must-appear-last-in-functions)
|
||||
* [Calling functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#calling-functions)
|
||||
* [Do not add spaces after opening or before closing bracket](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#do-not-add-spaces-after-opening-or-before-closing-bracket)
|
||||
* [Structures](https://github.com/RSS-Bridge/rss-bridge/wiki/Structures)
|
||||
* [Structures must always be formatted as multi-line blocks](https://github.com/RSS-Bridge/rss-bridge/wiki/Structures#structures-must-always-be-formatted-as-multi-line-blocks)
|
||||
* [If-Statement](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement)
|
||||
* [Use `elseif` instead of `else if`](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#use-elseif-instead-of-else-if)
|
||||
* [Do not write empty statements](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#do-not-write-empty-statements)
|
||||
* [Do not write unconditional if-statements](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#do-not-write-unconditional-if-statements)
|
||||
* [Classes](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes)
|
||||
* [Use PascalCase for class names](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#use-pascalcase-for-class-names)
|
||||
* [Do not use final statements inside final classes](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-use-final-statements-inside-final-classes)
|
||||
* [Do not override methods to call their parent](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-override-methods-to-call-their-parent)
|
||||
* [Casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting)
|
||||
* [Do not add spaces when casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting#do-not-add-spaces-when-casting)
|
||||
|
5
Dockerfile
Normal file
5
Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM ulsmith/alpine-apache-php7
|
||||
|
||||
COPY ./ /app/public/
|
||||
|
||||
RUN chown -R apache:root /app/public
|
226
README.md
226
README.md
@@ -1,10 +1,12 @@
|
||||
rss-bridge
|
||||
===
|
||||
[](UNLICENSE) [](https://travis-ci.org/RSS-Bridge/rss-bridge)
|
||||
[](UNLICENSE) [](https://github.com/rss-bridge/rss-bridge/releases/latest) [](https://tracker.debian.org/pkg/rss-bridge) [](https://www.gnu.org/software/guix/packages/R/) [](https://travis-ci.org/RSS-Bridge/rss-bridge) [](https://hub.docker.com/r/rssbridge/rss-bridge/)
|
||||
|
||||
rss-bridge is a PHP project capable of generating ATOM feeds for websites which don't have one.
|
||||
RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites which don't have one. It can be used on webservers or as stand alone application in CLI mode.
|
||||
|
||||
Supported sites/pages (main)
|
||||
**Important**: RSS-Bridge is __not__ a feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators).
|
||||
|
||||
Supported sites/pages (examples)
|
||||
===
|
||||
|
||||
* `Bandcamp` : Returns last release from [bandcamp](https://bandcamp.com/) for a tag
|
||||
@@ -25,106 +27,188 @@ Supported sites/pages (main)
|
||||
* `Wikipedia`: highlighted articles from [Wikipedia](https://wikipedia.org/) in English, German, French or Esperanto
|
||||
* `YouTube` : YouTube user channel, playlist or search
|
||||
|
||||
Plus [many other bridges](bridges/) to enable, thanks to the community
|
||||
And [many more](bridges/), thanks to the community!
|
||||
|
||||
Output format
|
||||
===
|
||||
Output format can take several forms:
|
||||
|
||||
* `Atom` : ATOM Feed, for use in RSS/Feed readers
|
||||
* `Html` : Simple html page.
|
||||
* `Json` : Json, for consumption by other applications.
|
||||
* `Mrss` : MRSS Feed, for use in RSS/Feed readers
|
||||
* `Plaintext` : raw text (php object, as returned by print_r)
|
||||
|
||||
RSS-Bridge is capable of producing several output formats:
|
||||
|
||||
* `Atom` : Atom feed, for use in feed readers
|
||||
* `Html` : Simple HTML page
|
||||
* `Json` : JSON, for consumption by other applications
|
||||
* `Mrss` : MRSS feed, for use in feed readers
|
||||
* `Plaintext` : Raw text, for consumption by other applications
|
||||
|
||||
You can extend RSS-Bridge with your own format, using the [Format API](https://github.com/RSS-Bridge/rss-bridge/wiki/Format-API)!
|
||||
|
||||
Screenshot
|
||||
===
|
||||
|
||||
Welcome screen:
|
||||
|
||||

|
||||
|
||||
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):
|
||||
|
||||

|
||||
|
||||
|
||||
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!
|
||||
|
||||
[](https://my.scalingo.com/deploy?source=https://github.com/sebsauvage/rss-bridge)
|
||||
|
||||
[](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://api.github.com/users/16mhz)
|
||||
* [Ahiles3005](https://api.github.com/users/Ahiles3005)
|
||||
* [Albirew](https://api.github.com/users/Albirew)
|
||||
* [AmauryCarrade](https://api.github.com/users/AmauryCarrade)
|
||||
* [ArthurHoaro](https://api.github.com/users/ArthurHoaro)
|
||||
* [Astalaseven](https://api.github.com/users/Astalaseven)
|
||||
* [Astyan-42](https://api.github.com/users/Astyan-42)
|
||||
* [Daiyousei](https://api.github.com/users/Daiyousei)
|
||||
* [Djuuu](https://api.github.com/users/Djuuu)
|
||||
* [Draeli](https://api.github.com/users/Draeli)
|
||||
* [EtienneM](https://api.github.com/users/EtienneM)
|
||||
* [Frenzie](https://api.github.com/users/Frenzie)
|
||||
* [Ginko-Aloe](https://api.github.com/users/Ginko-Aloe)
|
||||
* [Glandos](https://api.github.com/users/Glandos)
|
||||
* [GregThib](https://api.github.com/users/GregThib)
|
||||
* [Grummfy](https://api.github.com/users/Grummfy)
|
||||
* [JackNUMBER](https://api.github.com/users/JackNUMBER)
|
||||
* [JeremyRand](https://api.github.com/users/JeremyRand)
|
||||
* [Jocker666z](https://api.github.com/users/Jocker666z)
|
||||
* [LogMANOriginal](https://api.github.com/users/LogMANOriginal)
|
||||
* [MonsieurPoutounours](https://api.github.com/users/MonsieurPoutounours)
|
||||
* [ORelio](https://api.github.com/users/ORelio)
|
||||
* [PaulVayssiere](https://api.github.com/users/PaulVayssiere)
|
||||
* [Piranhaplant](https://api.github.com/users/Piranhaplant)
|
||||
* [Riduidel](https://api.github.com/users/Riduidel)
|
||||
* [Strubbl](https://api.github.com/users/Strubbl)
|
||||
* [TheRadialActive](https://api.github.com/users/TheRadialActive)
|
||||
* [TwizzyDizzy](https://api.github.com/users/TwizzyDizzy)
|
||||
* [WalterBarrett](https://api.github.com/users/WalterBarrett)
|
||||
* [ZeNairolf](https://api.github.com/users/ZeNairolf)
|
||||
* [adamchainz](https://api.github.com/users/adamchainz)
|
||||
* [aledeg](https://api.github.com/users/aledeg)
|
||||
* [alexAubin](https://api.github.com/users/alexAubin)
|
||||
* [az5he6ch](https://api.github.com/users/az5he6ch)
|
||||
* [b1nj](https://api.github.com/users/b1nj)
|
||||
* [benasse](https://api.github.com/users/benasse)
|
||||
* [captn3m0](https://api.github.com/users/captn3m0)
|
||||
* [chemel](https://api.github.com/users/chemel)
|
||||
* [ckiw](https://api.github.com/users/ckiw)
|
||||
* [cnlpete](https://api.github.com/users/cnlpete)
|
||||
* [corenting](https://api.github.com/users/corenting)
|
||||
* [da2x](https://api.github.com/users/da2x)
|
||||
* [eMerzh](https://api.github.com/users/eMerzh)
|
||||
* [em92](https://api.github.com/users/em92)
|
||||
* [griffaurel](https://api.github.com/users/griffaurel)
|
||||
* [hunhejj](https://api.github.com/users/hunhejj)
|
||||
* [j0k3r](https://api.github.com/users/j0k3r)
|
||||
* [jdigilio](https://api.github.com/users/jdigilio)
|
||||
* [kranack](https://api.github.com/users/kranack)
|
||||
* [kraoc](https://api.github.com/users/kraoc)
|
||||
* [laBecasse](https://api.github.com/users/laBecasse)
|
||||
* [lagaisse](https://api.github.com/users/lagaisse)
|
||||
* [lalannev](https://api.github.com/users/lalannev)
|
||||
* [ldidry](https://api.github.com/users/ldidry)
|
||||
* [m0zes](https://api.github.com/users/m0zes)
|
||||
* [matthewseal](https://api.github.com/users/matthewseal)
|
||||
* [mcbyte-it](https://api.github.com/users/mcbyte-it)
|
||||
* [mdemoss](https://api.github.com/users/mdemoss)
|
||||
* [melangue](https://api.github.com/users/melangue)
|
||||
* [metaMMA](https://api.github.com/users/metaMMA)
|
||||
* [mickael-bertrand](https://api.github.com/users/mickael-bertrand)
|
||||
* [mitsukarenai](https://api.github.com/users/mitsukarenai)
|
||||
* [mro](https://api.github.com/users/mro)
|
||||
* [mxmehl](https://api.github.com/users/mxmehl)
|
||||
* [nel50n](https://api.github.com/users/nel50n)
|
||||
* [niawag](https://api.github.com/users/niawag)
|
||||
* [pellaeon](https://api.github.com/users/pellaeon)
|
||||
* [pit-fgfjiudghdf](https://api.github.com/users/pit-fgfjiudghdf)
|
||||
* [pitchoule](https://api.github.com/users/pitchoule)
|
||||
* [pmaziere](https://api.github.com/users/pmaziere)
|
||||
* [prysme01](https://api.github.com/users/prysme01)
|
||||
* [quentinus95](https://api.github.com/users/quentinus95)
|
||||
* [qwertygc](https://api.github.com/users/qwertygc)
|
||||
* [regisenguehard](https://api.github.com/users/regisenguehard)
|
||||
* [rogerdc](https://api.github.com/users/rogerdc)
|
||||
* [sebsauvage](https://api.github.com/users/sebsauvage)
|
||||
* [sublimz](https://api.github.com/users/sublimz)
|
||||
* [sysadminstory](https://api.github.com/users/sysadminstory)
|
||||
* [tameroski](https://api.github.com/users/tameroski)
|
||||
* [teromene](https://api.github.com/users/teromene)
|
||||
* [triatic](https://api.github.com/users/triatic)
|
||||
* [wtuuju](https://api.github.com/users/wtuuju)
|
||||
|
||||
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 +217,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.
|
||||
|
@@ -8,7 +8,7 @@ class ABCTabsBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$html = getSimpleHTMLDOM(static::URI.'tablatures/nouveautes.html')
|
||||
$html = getSimpleHTMLDOM(static::URI . 'tablatures/nouveautes.html')
|
||||
or returnClientError('No results for this query.');
|
||||
|
||||
$table = $html->find('table#myTable', 0)->children(1);
|
||||
|
@@ -45,7 +45,7 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('category'))) {
|
||||
return self::NAME . ' : '
|
||||
.array_search(
|
||||
. array_search(
|
||||
$this->getInput('category'),
|
||||
self::PARAMETERS[$this->queriedContext]['category']['values']
|
||||
);
|
||||
|
@@ -52,7 +52,7 @@ class AmazonBridge extends BridgeAbstract {
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('tld')) && !is_null($this->getInput('q'))) {
|
||||
return 'Amazon.'.$this->getInput('tld').': '.$this->getInput('q');
|
||||
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('q');
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
@@ -60,8 +60,8 @@ class AmazonBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$uri = 'https://www.amazon.'.$this->getInput('tld').'/';
|
||||
$uri .= 's/?field-keywords='.urlencode($this->getInput('q')).'&sort='.$this->getInput('sort');
|
||||
$uri = 'https://www.amazon.' . $this->getInput('tld') . '/';
|
||||
$uri .= 's/?field-keywords=' . urlencode($this->getInput('q')) . '&sort=' . $this->getInput('sort');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not request Amazon.');
|
||||
@@ -86,7 +86,7 @@ class AmazonBridge extends BridgeAbstract {
|
||||
$price = $element->find('span.s-price', 0);
|
||||
$price = ($price) ? $price->innertext : '';
|
||||
|
||||
$item['content'] = '<img src="'.$image->getAttribute('src').'" /><br />'.$price;
|
||||
$item['content'] = '<img src="' . $image->getAttribute('src') . '" /><br />' . $price;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
187
bridges/AmazonPriceTrackerBridge.php
Normal file
187
bridges/AmazonPriceTrackerBridge.php
Normal 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
207
bridges/AnidexBridge.php
Normal 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;
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
const NAME = 'Anime-Ultime';
|
||||
const URI = 'http://www.anime-ultime.net/';
|
||||
const CACHE_TIMEOUT = 10800; // 3h
|
||||
const DESCRIPTION = 'Returns the 10 newest releases posted on Anime-Ultime';
|
||||
const DESCRIPTION = 'Returns the newest releases posted on Anime-Ultime.';
|
||||
const PARAMETERS = array( array(
|
||||
'type' => array(
|
||||
'name' => 'Type',
|
||||
@@ -65,6 +65,13 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
$item_link_element = $release->find('td', 0)->find('a', 0);
|
||||
$item_uri = self::URI . $item_link_element->href;
|
||||
$item_name = html_entity_decode($item_link_element->plaintext);
|
||||
|
||||
$item_image = self::URI . substr(
|
||||
$item_link_element->onmouseover,
|
||||
37,
|
||||
strpos($item_link_element->onmouseover, ' ', 37) - 37
|
||||
);
|
||||
|
||||
$item_episode = html_entity_decode(
|
||||
str_pad(
|
||||
$release->find('td', 1)->plaintext,
|
||||
@@ -79,8 +86,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
|
||||
if(!empty($item_uri)) {
|
||||
|
||||
// Retrieve description from description page and
|
||||
// convert relative image src info absolute image src
|
||||
// Retrieve description from description page
|
||||
$html_item = getContents($item_uri)
|
||||
or returnServerError('Could not request Anime-Ultime: ' . $item_uri);
|
||||
$item_description = substr(
|
||||
@@ -91,10 +97,9 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
0,
|
||||
strpos($item_description, '<div id="table">')
|
||||
);
|
||||
$item_description = str_replace(
|
||||
'src="images', 'src="' . self::URI . 'images',
|
||||
$item_description
|
||||
);
|
||||
|
||||
// Convert relative image src into absolute image src, remove line breaks
|
||||
$item_description = defaultLinkTo($item_description, self::URI);
|
||||
$item_description = str_replace("\r", '', $item_description);
|
||||
$item_description = str_replace("\n", '', $item_description);
|
||||
$item_description = utf8_encode($item_description);
|
||||
@@ -105,6 +110,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
$item['title'] = $item_name . ' ' . $item_type . ' ' . $item_episode;
|
||||
$item['author'] = $item_fansub;
|
||||
$item['timestamp'] = $item_date;
|
||||
$item['enclosures'] = array($item_image);
|
||||
$item['content'] = $item_description;
|
||||
$this->items[] = $item;
|
||||
$processedOK++;
|
||||
|
@@ -28,6 +28,13 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
)
|
||||
)
|
||||
),
|
||||
'Collection (Français)' => array(
|
||||
'colfr' => array(
|
||||
'name' => 'Collection id',
|
||||
'required' => true,
|
||||
'title' => 'ex. RC-014095 pour https://www.arte.tv/fr/videos/RC-014095/blow-up/'
|
||||
)
|
||||
),
|
||||
'Catégorie (Allemand)' => array(
|
||||
'catde' => array(
|
||||
'type' => 'list',
|
||||
@@ -45,6 +52,13 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
'Sonstiges' => 'AUT'
|
||||
)
|
||||
)
|
||||
),
|
||||
'Collection (Allemand)' => array(
|
||||
'colde' => array(
|
||||
'name' => 'Collection id',
|
||||
'required' => true,
|
||||
'title' => 'ex. RC-014095 pour https://www.arte.tv/de/videos/RC-014095/blow-up/'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -54,23 +68,30 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
$category = $this->getInput('catfr');
|
||||
$lang = 'fr';
|
||||
break;
|
||||
case 'Collection (Français)':
|
||||
$lang = 'fr';
|
||||
$collectionId = $this->getInput('colfr');
|
||||
break;
|
||||
case 'Catégorie (Allemand)':
|
||||
$category = $this->getInput('catde');
|
||||
$lang = 'de';
|
||||
break;
|
||||
case 'Collection (Allemand)':
|
||||
$lang = 'de';
|
||||
$collectionId = $this->getInput('colde');
|
||||
break;
|
||||
}
|
||||
|
||||
$url = 'https://api.arte.tv/api/opa/v3/videos?sort=-lastModified&limit=10&language='
|
||||
. $lang
|
||||
. ($category != null ? '&category.code=' . $category : '');
|
||||
. ($category != null ? '&category.code=' . $category : '')
|
||||
. ($collectionId != null ? '&collections.collectionId=' . $collectionId : '');
|
||||
|
||||
$context = array(
|
||||
'http' => array(
|
||||
'header' => 'Authorization: Bearer '. self::API_TOKEN
|
||||
)
|
||||
$header = array(
|
||||
'Authorization: Bearer ' . self::API_TOKEN
|
||||
);
|
||||
|
||||
$input = getContents($url, false, stream_context_create($context)) or die('Could not request ARTE.');
|
||||
$input = getContents($url, $header) or die('Could not request ARTE.');
|
||||
$input_json = json_decode($input, true);
|
||||
|
||||
foreach($input_json['videos'] as $element) {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class AskfmBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'az5he6ch';
|
||||
const MAINTAINER = 'az5he6ch, logmanoriginal';
|
||||
const NAME = 'Ask.fm Answers';
|
||||
const URI = 'https://ask.fm/';
|
||||
const CACHE_TIMEOUT = 300; //5 min
|
||||
@@ -19,39 +19,39 @@ class AskfmBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Requested username can\'t be found.');
|
||||
|
||||
foreach($html->find('div.streamItem-answer') as $element) {
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
foreach($html->find('article.streamItem-answer') as $element) {
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $element->find('a.streamItemsAge', 0)->href;
|
||||
$question = trim($element->find('h1.streamItemContent-question', 0)->innertext);
|
||||
$item['uri'] = $element->find('a.streamItem_meta', 0)->href;
|
||||
$question = trim($element->find('header.streamItem_header', 0)->innertext);
|
||||
|
||||
$item['title'] = trim(
|
||||
htmlspecialchars_decode($element->find('h1.streamItemContent-question', 0)->plaintext,
|
||||
htmlspecialchars_decode($element->find('header.streamItem_header', 0)->plaintext,
|
||||
ENT_QUOTES
|
||||
)
|
||||
);
|
||||
|
||||
$answer = trim($element->find('p.streamItemContent-answer', 0)->innertext);
|
||||
$item['timestamp'] = strtotime($element->find('time', 0)->datetime);
|
||||
|
||||
// Doesn't work, DOM parser doesn't seem to like data-hint, dunno why
|
||||
#$item['update'] = $element->find('a.streamitemsage',0)->data-hint;
|
||||
$answer = trim($element->find('div.streamItem_content', 0)->innertext);
|
||||
|
||||
// This probably should be cleaned up, especially for YouTube embeds
|
||||
$visual = $element->find('div.streamItemContent-visual', 0)->innertext;
|
||||
//Fix tracking links, also doesn't work
|
||||
if($visual = $element->find('div.streamItem_visual', 0)) {
|
||||
$visual = $visual->innertext;
|
||||
}
|
||||
|
||||
// Fix tracking links, also doesn't work
|
||||
foreach($element->find('a') as $link) {
|
||||
if(strpos($link->href, 'l.ask.fm') !== false) {
|
||||
|
||||
// Too slow
|
||||
#$link->href = str_replace('#_=_', '', get_headers($link->href, 1)['Location']);
|
||||
|
||||
$link->href = $link->plaintext;
|
||||
}
|
||||
}
|
||||
|
||||
$content = '<p>' . $question . '</p><p>' . $answer . '</p><p>' . $visual . '</p>';
|
||||
// Fix relative links without breaking // scheme used by YouTube stuff
|
||||
$content = preg_replace('#href="\/(?!\/)#', 'href="' . self::URI, $content);
|
||||
$item['content'] = $content;
|
||||
$item['content'] = '<p>' . $question
|
||||
. '</p><p>' . $answer
|
||||
. '</p><p>' . $visual . '</p>';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class AskfmBridge extends BridgeAbstract {
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return self::URI . urlencode($this->getInput('u')) . '/answers/more?page=0';
|
||||
return self::URI . urlencode($this->getInput('u'));
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
|
65
bridges/AutoJMBridge.php
Normal file
65
bridges/AutoJMBridge.php
Normal 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
265
bridges/BAEBridge.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
class BAEBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'couraudt';
|
||||
const NAME = 'Bourse Aux Equipiers Bridge';
|
||||
const URI = 'https://www.bourse-aux-equipiers.com';
|
||||
const DESCRIPTION = 'Returns the newest sailing offers.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'keyword' => array(
|
||||
'name' => 'Filtrer par mots clés',
|
||||
'title' => 'Entrez le mot clé à filtrer ici'
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Type de recherche',
|
||||
'title' => 'Afficher seuleument un certain type d\'annonce',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Toutes les annonces' => false,
|
||||
'Les embarquements' => 'boat',
|
||||
'Les skippers' => 'skipper',
|
||||
'Les équipiers' => 'crew'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getURI();
|
||||
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
|
||||
|
||||
$annonces = $html->find('main article');
|
||||
foreach ($annonces as $annonce) {
|
||||
$detail = $annonce->find('footer a', 0);
|
||||
|
||||
$htmlDetail = getSimpleHTMLDOMCached(parent::getURI() . $detail->href);
|
||||
if (!$htmlDetail)
|
||||
continue;
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $annonce->find('header h2', 0)->plaintext;
|
||||
$item['uri'] = parent::getURI() . $detail->href;
|
||||
|
||||
$content = $htmlDetail->find('article p', 0)->innertext;
|
||||
if (!empty($this->getInput('keyword'))) {
|
||||
$keyword = $this->remove_accents(strtolower($this->getInput('keyword')));
|
||||
$cleanTitle = $this->remove_accents(strtolower($item['title']));
|
||||
if (strpos($cleanTitle, $keyword) === false) {
|
||||
$cleanContent = $this->remove_accents(strtolower($content));
|
||||
if (strpos($cleanContent, $keyword) === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$content .= '<hr>';
|
||||
$content .= $htmlDetail->find('section', 0)->innertext;
|
||||
$content = str_replace('src="/', 'src="' . parent::getURI() . '/', $content);
|
||||
$content = str_replace('href="/', 'href="' . parent::getURI() . '/', $content);
|
||||
$item['content'] = $content;
|
||||
$image = $htmlDetail->find('#zoom', 0);
|
||||
if ($image) {
|
||||
$item['enclosures'] = array(parent::getURI() . $image->getAttribute('src'));
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = parent::getURI();
|
||||
if (!empty($this->getInput('type'))) {
|
||||
if ($this->getInput('type') == 'boat') {
|
||||
$uri .= '/embarquements.html';
|
||||
} elseif ($this->getInput('type') == 'skipper') {
|
||||
$uri .= '/skippers.html';
|
||||
} else {
|
||||
$uri .= '/equipiers.html';
|
||||
}
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
private function remove_accents($string) {
|
||||
$chars = array(
|
||||
// Decompositions for Latin-1 Supplement
|
||||
'ª' => 'a', 'º' => 'o',
|
||||
'À' => 'A', 'Á' => 'A',
|
||||
'Â' => 'A', 'Ã' => 'A',
|
||||
'Ä' => 'A', 'Å' => 'A',
|
||||
'Æ' => 'AE', 'Ç' => 'C',
|
||||
'È' => 'E', 'É' => 'E',
|
||||
'Ê' => 'E', 'Ë' => 'E',
|
||||
'Ì' => 'I', 'Í' => 'I',
|
||||
'Î' => 'I', 'Ï' => 'I',
|
||||
'Ð' => 'D', 'Ñ' => 'N',
|
||||
'Ò' => 'O', 'Ó' => 'O',
|
||||
'Ô' => 'O', 'Õ' => 'O',
|
||||
'Ö' => 'O', 'Ù' => 'U',
|
||||
'Ú' => 'U', 'Û' => 'U',
|
||||
'Ü' => 'U', 'Ý' => 'Y',
|
||||
'Þ' => 'TH', 'ß' => 's',
|
||||
'à' => 'a', 'á' => 'a',
|
||||
'â' => 'a', 'ã' => 'a',
|
||||
'ä' => 'a', 'å' => 'a',
|
||||
'æ' => 'ae', 'ç' => 'c',
|
||||
'è' => 'e', 'é' => 'e',
|
||||
'ê' => 'e', 'ë' => 'e',
|
||||
'ì' => 'i', 'í' => 'i',
|
||||
'î' => 'i', 'ï' => 'i',
|
||||
'ð' => 'd', 'ñ' => 'n',
|
||||
'ò' => 'o', 'ó' => 'o',
|
||||
'ô' => 'o', 'õ' => 'o',
|
||||
'ö' => 'o', 'ø' => 'o',
|
||||
'ù' => 'u', 'ú' => 'u',
|
||||
'û' => 'u', 'ü' => 'u',
|
||||
'ý' => 'y', 'þ' => 'th',
|
||||
'ÿ' => 'y', 'Ø' => 'O',
|
||||
// Decompositions for Latin Extended-A
|
||||
'Ā' => 'A', 'ā' => 'a',
|
||||
'Ă' => 'A', 'ă' => 'a',
|
||||
'Ą' => 'A', 'ą' => 'a',
|
||||
'Ć' => 'C', 'ć' => 'c',
|
||||
'Ĉ' => 'C', 'ĉ' => 'c',
|
||||
'Ċ' => 'C', 'ċ' => 'c',
|
||||
'Č' => 'C', 'č' => 'c',
|
||||
'Ď' => 'D', 'ď' => 'd',
|
||||
'Đ' => 'D', 'đ' => 'd',
|
||||
'Ē' => 'E', 'ē' => 'e',
|
||||
'Ĕ' => 'E', 'ĕ' => 'e',
|
||||
'Ė' => 'E', 'ė' => 'e',
|
||||
'Ę' => 'E', 'ę' => 'e',
|
||||
'Ě' => 'E', 'ě' => 'e',
|
||||
'Ĝ' => 'G', 'ĝ' => 'g',
|
||||
'Ğ' => 'G', 'ğ' => 'g',
|
||||
'Ġ' => 'G', 'ġ' => 'g',
|
||||
'Ģ' => 'G', 'ģ' => 'g',
|
||||
'Ĥ' => 'H', 'ĥ' => 'h',
|
||||
'Ħ' => 'H', 'ħ' => 'h',
|
||||
'Ĩ' => 'I', 'ĩ' => 'i',
|
||||
'Ī' => 'I', 'ī' => 'i',
|
||||
'Ĭ' => 'I', 'ĭ' => 'i',
|
||||
'Į' => 'I', 'į' => 'i',
|
||||
'İ' => 'I', 'ı' => 'i',
|
||||
'IJ' => 'IJ', 'ij' => 'ij',
|
||||
'Ĵ' => 'J', 'ĵ' => 'j',
|
||||
'Ķ' => 'K', 'ķ' => 'k',
|
||||
'ĸ' => 'k', 'Ĺ' => 'L',
|
||||
'ĺ' => 'l', 'Ļ' => 'L',
|
||||
'ļ' => 'l', 'Ľ' => 'L',
|
||||
'ľ' => 'l', 'Ŀ' => 'L',
|
||||
'ŀ' => 'l', 'Ł' => 'L',
|
||||
'ł' => 'l', 'Ń' => 'N',
|
||||
'ń' => 'n', 'Ņ' => 'N',
|
||||
'ņ' => 'n', 'Ň' => 'N',
|
||||
'ň' => 'n', 'ʼn' => 'n',
|
||||
'Ŋ' => 'N', 'ŋ' => 'n',
|
||||
'Ō' => 'O', 'ō' => 'o',
|
||||
'Ŏ' => 'O', 'ŏ' => 'o',
|
||||
'Ő' => 'O', 'ő' => 'o',
|
||||
'Œ' => 'OE', 'œ' => 'oe',
|
||||
'Ŕ' => 'R', 'ŕ' => 'r',
|
||||
'Ŗ' => 'R', 'ŗ' => 'r',
|
||||
'Ř' => 'R', 'ř' => 'r',
|
||||
'Ś' => 'S', 'ś' => 's',
|
||||
'Ŝ' => 'S', 'ŝ' => 's',
|
||||
'Ş' => 'S', 'ş' => 's',
|
||||
'Š' => 'S', 'š' => 's',
|
||||
'Ţ' => 'T', 'ţ' => 't',
|
||||
'Ť' => 'T', 'ť' => 't',
|
||||
'Ŧ' => 'T', 'ŧ' => 't',
|
||||
'Ũ' => 'U', 'ũ' => 'u',
|
||||
'Ū' => 'U', 'ū' => 'u',
|
||||
'Ŭ' => 'U', 'ŭ' => 'u',
|
||||
'Ů' => 'U', 'ů' => 'u',
|
||||
'Ű' => 'U', 'ű' => 'u',
|
||||
'Ų' => 'U', 'ų' => 'u',
|
||||
'Ŵ' => 'W', 'ŵ' => 'w',
|
||||
'Ŷ' => 'Y', 'ŷ' => 'y',
|
||||
'Ÿ' => 'Y', 'Ź' => 'Z',
|
||||
'ź' => 'z', 'Ż' => 'Z',
|
||||
'ż' => 'z', 'Ž' => 'Z',
|
||||
'ž' => 'z', 'ſ' => 's',
|
||||
// Decompositions for Latin Extended-B
|
||||
'Ș' => 'S', 'ș' => 's',
|
||||
'Ț' => 'T', 'ț' => 't',
|
||||
// Euro Sign
|
||||
'€' => 'E',
|
||||
// GBP (Pound) Sign
|
||||
'£' => '',
|
||||
// Vowels with diacritic (Vietnamese)
|
||||
// unmarked
|
||||
'Ơ' => 'O', 'ơ' => 'o',
|
||||
'Ư' => 'U', 'ư' => 'u',
|
||||
// grave accent
|
||||
'Ầ' => 'A', 'ầ' => 'a',
|
||||
'Ằ' => 'A', 'ằ' => 'a',
|
||||
'Ề' => 'E', 'ề' => 'e',
|
||||
'Ồ' => 'O', 'ồ' => 'o',
|
||||
'Ờ' => 'O', 'ờ' => 'o',
|
||||
'Ừ' => 'U', 'ừ' => 'u',
|
||||
'Ỳ' => 'Y', 'ỳ' => 'y',
|
||||
// hook
|
||||
'Ả' => 'A', 'ả' => 'a',
|
||||
'Ẩ' => 'A', 'ẩ' => 'a',
|
||||
'Ẳ' => 'A', 'ẳ' => 'a',
|
||||
'Ẻ' => 'E', 'ẻ' => 'e',
|
||||
'Ể' => 'E', 'ể' => 'e',
|
||||
'Ỉ' => 'I', 'ỉ' => 'i',
|
||||
'Ỏ' => 'O', 'ỏ' => 'o',
|
||||
'Ổ' => 'O', 'ổ' => 'o',
|
||||
'Ở' => 'O', 'ở' => 'o',
|
||||
'Ủ' => 'U', 'ủ' => 'u',
|
||||
'Ử' => 'U', 'ử' => 'u',
|
||||
'Ỷ' => 'Y', 'ỷ' => 'y',
|
||||
// tilde
|
||||
'Ẫ' => 'A', 'ẫ' => 'a',
|
||||
'Ẵ' => 'A', 'ẵ' => 'a',
|
||||
'Ẽ' => 'E', 'ẽ' => 'e',
|
||||
'Ễ' => 'E', 'ễ' => 'e',
|
||||
'Ỗ' => 'O', 'ỗ' => 'o',
|
||||
'Ỡ' => 'O', 'ỡ' => 'o',
|
||||
'Ữ' => 'U', 'ữ' => 'u',
|
||||
'Ỹ' => 'Y', 'ỹ' => 'y',
|
||||
// acute accent
|
||||
'Ấ' => 'A', 'ấ' => 'a',
|
||||
'Ắ' => 'A', 'ắ' => 'a',
|
||||
'Ế' => 'E', 'ế' => 'e',
|
||||
'Ố' => 'O', 'ố' => 'o',
|
||||
'Ớ' => 'O', 'ớ' => 'o',
|
||||
'Ứ' => 'U', 'ứ' => 'u',
|
||||
// dot below
|
||||
'Ạ' => 'A', 'ạ' => 'a',
|
||||
'Ậ' => 'A', 'ậ' => 'a',
|
||||
'Ặ' => 'A', 'ặ' => 'a',
|
||||
'Ẹ' => 'E', 'ẹ' => 'e',
|
||||
'Ệ' => 'E', 'ệ' => 'e',
|
||||
'Ị' => 'I', 'ị' => 'i',
|
||||
'Ọ' => 'O', 'ọ' => 'o',
|
||||
'Ộ' => 'O', 'ộ' => 'o',
|
||||
'Ợ' => 'O', 'ợ' => 'o',
|
||||
'Ụ' => 'U', 'ụ' => 'u',
|
||||
'Ự' => 'U', 'ự' => 'u',
|
||||
'Ỵ' => 'Y', 'ỵ' => 'y',
|
||||
// Vowels with diacritic (Chinese, Hanyu Pinyin)
|
||||
'ɑ' => 'a',
|
||||
// macron
|
||||
'Ǖ' => 'U', 'ǖ' => 'u',
|
||||
// acute accent
|
||||
'Ǘ' => 'U', 'ǘ' => 'u',
|
||||
// caron
|
||||
'Ǎ' => 'A', 'ǎ' => 'a',
|
||||
'Ǐ' => 'I', 'ǐ' => 'i',
|
||||
'Ǒ' => 'O', 'ǒ' => 'o',
|
||||
'Ǔ' => 'U', 'ǔ' => 'u',
|
||||
'Ǚ' => 'U', 'ǚ' => 'u',
|
||||
// grave accent
|
||||
'Ǜ' => 'U', 'ǜ' => 'u',
|
||||
);
|
||||
|
||||
$string = strtr($string, $chars);
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
@@ -14,6 +14,10 @@ class BandcampBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://s4.bcbits.com/img/bc_favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('No results for this query.');
|
||||
|
@@ -1,31 +1,47 @@
|
||||
<?php
|
||||
class BlaguesDeMerdeBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'superbaillot.net';
|
||||
const MAINTAINER = 'superbaillot.net, logmanoriginal';
|
||||
const NAME = 'Blagues De Merde';
|
||||
const URI = 'http://www.blaguesdemerde.fr/';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Blagues De Merde';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/img/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request BDM.');
|
||||
|
||||
foreach($html->find('article.joke_contener') as $element) {
|
||||
$item = array();
|
||||
$temp = $element->find('a');
|
||||
foreach($html->find('div.blague') as $element) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = static::URI . '#' . $element->id;
|
||||
$item['author'] = $element->find('div[class="blague-footer"] p strong', 0)->plaintext;
|
||||
|
||||
// Let the title be everything up to the first <br>
|
||||
$item['title'] = trim(explode("\n", $element->find('div.text', 0)->plaintext)[0]);
|
||||
|
||||
$item['content'] = strip_tags($element->find('div.text', 0));
|
||||
|
||||
// timestamp is part of:
|
||||
// <p>Par <strong>{author}</strong> le {date} dans <strong>{category}</strong></p>
|
||||
preg_match(
|
||||
'/.+le(.+)dans.*/',
|
||||
$element->find('div[class="blague-footer"]', 0)->plaintext,
|
||||
$matches
|
||||
);
|
||||
|
||||
$item['timestamp'] = strtotime($matches[1]);
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if(isset($temp[2])) {
|
||||
$item['content'] = trim($element->find('div.joke_text_contener', 0)->innertext);
|
||||
$uri = $temp[2]->href;
|
||||
$item['uri'] = $uri;
|
||||
$item['title'] = substr($uri, (strrpos($uri, "/") + 1));
|
||||
$date = $element->find('li.bdm_date', 0)->innertext;
|
||||
$time = mktime(0, 0, 0, substr($date, 3, 2), substr($date, 0, 2), substr($date, 6, 4));
|
||||
$item['timestamp'] = $time;
|
||||
$item['author'] = $element->find('li.bdm_pseudo', 0)->innertext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -31,6 +31,10 @@ class BloombergBridge extends BridgeAbstract
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://assets.bwbx.io/s3/javelin/public/hub/images/favicon-black-63fe5249d3.png';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
switch($this->queriedContext) {
|
||||
|
87
bridges/BundesbankBridge.php
Normal file
87
bridges/BundesbankBridge.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
class CADBridge extends FeedExpander {
|
||||
const MAINTAINER = 'nyutag';
|
||||
const NAME = 'CAD Bridge';
|
||||
const URI = 'http://www.cad-comic.com/';
|
||||
const CACHE_TIMEOUT = 7200; //2h
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas('http://cdn2.cad-comic.com/rss.xml', 10);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
$item['content'] = $this->extractCADContent($item['uri']);
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function extractCADContent($url) {
|
||||
$html3 = getSimpleHTMLDOMCached($url);
|
||||
|
||||
// The request might fail due to missing https support or wrong URL
|
||||
if($html3 == false)
|
||||
return 'Daily comic not released yet';
|
||||
|
||||
$htmlpart = explode("/", $url);
|
||||
|
||||
switch ($htmlpart[3]) {
|
||||
case 'cad':
|
||||
preg_match_all("/http:\/\/cdn2\.cad-comic\.com\/comics\/cad-\S*png/", $html3, $url2);
|
||||
break;
|
||||
case 'sillies':
|
||||
preg_match_all("/http:\/\/cdn2\.cad-comic\.com\/comics\/sillies-\S*gif/", $html3, $url2);
|
||||
break;
|
||||
default:
|
||||
return 'Daily comic not released yet';
|
||||
}
|
||||
$img = implode($url2[0]);
|
||||
$html3->clear();
|
||||
unset($html3);
|
||||
if ($img == '')
|
||||
return 'Daily comic not released yet';
|
||||
return '<img src="' . $img . '"/>';
|
||||
}
|
||||
}
|
@@ -3,91 +3,107 @@ class CNETBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'CNET News';
|
||||
const URI = 'http://www.cnet.com/';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Returns the newest articles. <br /> You may specify a
|
||||
topic found in some section URLs, else all topics are selected.';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'topic' => array(
|
||||
'name' => 'Topic name'
|
||||
const URI = 'https://www.cnet.com/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'topic' => array(
|
||||
'name' => 'Topic',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All articles' => '',
|
||||
'Apple' => 'apple',
|
||||
'Google' => 'google',
|
||||
'Microsoft' => 'tags-microsoft',
|
||||
'Computers' => 'topics-computers',
|
||||
'Mobile' => 'topics-mobile',
|
||||
'Sci-Tech' => 'topics-sci-tech',
|
||||
'Security' => 'topics-security',
|
||||
'Internet' => 'topics-internet',
|
||||
'Tech Industry' => 'topics-tech-industry'
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
private function cleanArticle($article_html) {
|
||||
$offset_p = strpos($article_html, '<p>');
|
||||
$offset_figure = strpos($article_html, '<figure');
|
||||
$offset = ($offset_figure < $offset_p ? $offset_figure : $offset_p);
|
||||
$article_html = substr($article_html, $offset);
|
||||
$article_html = str_replace('href="/', 'href="' . self::URI, $article_html);
|
||||
$article_html = str_replace(' height="0"', '', $article_html);
|
||||
$article_html = str_replace('<noscript>', '', $article_html);
|
||||
$article_html = str_replace('</noscript>', '', $article_html);
|
||||
$article_html = StripWithDelimiters($article_html, '<a class="clickToEnlarge', '</a>');
|
||||
$article_html = stripWithDelimiters($article_html, '<span class="nowPlaying', '</span>');
|
||||
$article_html = stripWithDelimiters($article_html, '<span class="duration', '</span>');
|
||||
$article_html = stripWithDelimiters($article_html, '<script', '</script>');
|
||||
$article_html = stripWithDelimiters($article_html, '<svg', '</svg>');
|
||||
return $article_html;
|
||||
}
|
||||
|
||||
function extractFromDelimiters($string, $start, $end){
|
||||
if(strpos($string, $start) !== false) {
|
||||
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
|
||||
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
|
||||
return $section_retrieved;
|
||||
public function collectData() {
|
||||
|
||||
// Retrieve and check user input
|
||||
$topic = str_replace('-', '/', $this->getInput('topic'));
|
||||
if (!empty($topic) && (substr_count($topic, '/') > 1 || !ctype_alpha(str_replace('/', '', $topic))))
|
||||
returnClientError('Invalid topic: ' . $topic);
|
||||
|
||||
// Retrieve webpage
|
||||
$pageUrl = self::URI . (empty($topic) ? 'news/' : $topic . '/');
|
||||
$html = getSimpleHTMLDOM($pageUrl)
|
||||
or returnServerError('Could not request CNET: ' . $pageUrl);
|
||||
|
||||
// Process articles
|
||||
foreach($html->find('div.assetBody, div.riverPost') as $element) {
|
||||
|
||||
if(count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
$article_title = trim($element->find('h2, h3', 0)->plaintext);
|
||||
$article_uri = self::URI . substr($element->find('a', 0)->href, 1);
|
||||
$article_thumbnail = $element->parent()->find('img[src]', 0)->src;
|
||||
$article_timestamp = strtotime($element->find('time.assetTime, div.timeAgo', 0)->plaintext);
|
||||
$article_author = trim($element->find('a[rel=author], a.name', 0)->plaintext);
|
||||
$article_content = '<p><b>' . trim($element->find('p.dek', 0)->plaintext) . '</b></p>';
|
||||
|
||||
function stripWithDelimiters($string, $start, $end){
|
||||
while(strpos($string, $start) !== false) {
|
||||
$section_to_remove = substr($string, strpos($string, $start));
|
||||
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
|
||||
$string = str_replace($section_to_remove, '', $string);
|
||||
}
|
||||
if (is_null($article_thumbnail))
|
||||
$article_thumbnail = extractFromDelimiters($element->innertext, '<img src="', '"');
|
||||
|
||||
return $string;
|
||||
}
|
||||
if (!empty($article_title) && !empty($article_uri) && strpos($article_uri, self::URI . 'news/') !== false) {
|
||||
|
||||
function cleanArticle($article_html){
|
||||
$article_html = '<p>' . substr($article_html, strpos($article_html, '<p>') + 3);
|
||||
$article_html = stripWithDelimiters($article_html, '<span class="credit">', '</span>');
|
||||
$article_html = stripWithDelimiters($article_html, '<script', '</script>');
|
||||
$article_html = stripWithDelimiters($article_html, '<div class="shortcode related-links', '</div>');
|
||||
$article_html = stripWithDelimiters($article_html, '<a class="clickToEnlarge">', '</a>');
|
||||
return $article_html;
|
||||
}
|
||||
$article_html = getSimpleHTMLDOMCached($article_uri) or $article_html = null;
|
||||
|
||||
$pageUrl = self::URI . (empty($this->getInput('topic')) ? '' : 'topics/' . $this->getInput('topic') . '/');
|
||||
$html = getSimpleHTMLDOM($pageUrl) or returnServerError('Could not request CNET: ' . $pageUrl);
|
||||
$limit = 0;
|
||||
if (!is_null($article_html)) {
|
||||
|
||||
foreach($html->find('div.assetBody') as $element) {
|
||||
if($limit < 8) {
|
||||
$article_title = trim($element->find('h2', 0)->plaintext);
|
||||
$article_uri = self::URI . ($element->find('a', 0)->href);
|
||||
$article_timestamp = strtotime($element->find('time.assetTime', 0)->plaintext);
|
||||
$article_author = trim($element->find('a[rel=author]', 0)->plaintext);
|
||||
if (empty($article_thumbnail))
|
||||
$article_thumbnail = $article_html->find('div.originalImage', 0);
|
||||
if (empty($article_thumbnail))
|
||||
$article_thumbnail = $article_html->find('span.imageContainer', 0);
|
||||
if (is_object($article_thumbnail))
|
||||
$article_thumbnail = $article_thumbnail->find('img', 0)->src;
|
||||
|
||||
if(!empty($article_title) && !empty($article_uri) && strpos($article_uri, '/news/') !== false) {
|
||||
$article_html = getSimpleHTMLDOM($article_uri)
|
||||
or returnServerError('Could not request CNET: ' . $article_uri);
|
||||
$article_content = trim(
|
||||
cleanArticle(
|
||||
$article_content .= trim(
|
||||
$this->cleanArticle(
|
||||
extractFromDelimiters(
|
||||
$article_html,
|
||||
'<div class="articleContent',
|
||||
'<footer>'
|
||||
$article_html, '<article', '<footer'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $article_uri;
|
||||
$item['title'] = $article_title;
|
||||
$item['author'] = $article_author;
|
||||
$item['timestamp'] = $article_timestamp;
|
||||
$item['content'] = $article_content;
|
||||
$this->items[] = $item;
|
||||
$limit++;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $article_uri;
|
||||
$item['title'] = $article_title;
|
||||
$item['author'] = $article_author;
|
||||
$item['timestamp'] = $article_timestamp;
|
||||
$item['enclosures'] = array($article_thumbnail);
|
||||
$item['content'] = $article_content;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('topic'))) {
|
||||
$topic = $this->getInput('topic');
|
||||
return 'CNET News Bridge' . (empty($topic) ? '' : ' - ' . $topic);
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
28
bridges/ChristianDailyReporterBridge.php
Normal file
28
bridges/ChristianDailyReporterBridge.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
97
bridges/ContainerLinuxReleasesBridge.php
Normal file
97
bridges/ContainerLinuxReleasesBridge.php
Normal 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();
|
||||
}
|
||||
}
|
@@ -25,7 +25,7 @@ class CopieDoubleBridge extends BridgeAbstract {
|
||||
} 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('src="/', 'src="/' . self::URI, $element->find('td', 0)->innertext);
|
||||
$content = str_replace('href="/', 'href="' . self::URI, $content);
|
||||
$item['content'] = $content;
|
||||
$this->items[] = $item;
|
||||
|
@@ -11,7 +11,7 @@ class CourrierInternationalBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Error.');
|
||||
|
||||
$element = $html->find("article");
|
||||
$element = $html->find('article');
|
||||
$article_count = 1;
|
||||
|
||||
foreach($element as $article) {
|
||||
|
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
class CpasbienBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'lagaisse';
|
||||
const NAME = 'Cpasbien Bridge';
|
||||
const URI = 'http://www.cpasbien.cm';
|
||||
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') {
|
||||
|
||||
$urlepisode = $episode->find('a', 0)->getAttribute('href');
|
||||
$htmlepisode = getSimpleHTMLDOMCached($urlepisode, 86400 * 366 * 30);
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $episode->find('a', 0)->text();
|
||||
$item['title'] = $episode->find('a', 0)->text();
|
||||
$item['pubdate'] = $this->getCachedDate($urlepisode);
|
||||
$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(){
|
||||
if(!is_null($this->getInput('q'))) {
|
||||
return $this->getInput('q') . ' : ' . self::NAME;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getCachedDate($url){
|
||||
debugMessage('getting pubdate from url ' . $url . '');
|
||||
|
||||
// Initialize cache
|
||||
$cache = Cache::create('FileCache');
|
||||
$cache->setPath(CACHE_DIR . '/pages');
|
||||
|
||||
$params = [$url];
|
||||
$cache->setParameters($params);
|
||||
|
||||
// Get cachefile timestamp
|
||||
$time = $cache->getTime();
|
||||
return ($time !== false ? $time : time());
|
||||
}
|
||||
}
|
@@ -48,6 +48,10 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static1-ssl.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vf806ca4ed0deed812';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$limit = 5;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class DanbooruBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
const MAINTAINER = 'mitsukarenai, logmanoriginal';
|
||||
const NAME = 'Danbooru';
|
||||
const URI = 'http://donmai.us/';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
@@ -41,7 +41,7 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
$item['postid'] = (int)preg_replace("/[^0-9]/", '', $element->getAttribute(static::IDATTRIBUTE));
|
||||
$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);
|
||||
@@ -57,11 +57,80 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getFullURI())
|
||||
$content = getContents($this->getFullURI())
|
||||
or returnServerError('Could not request ' . $this->getName());
|
||||
|
||||
$html = Fix_Simple_Html_Dom::str_get_html($content);
|
||||
|
||||
foreach($html->find(static::PATHTODATA) as $element) {
|
||||
$this->items[] = $this->getItemFromElement($element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is a monkey patch to 'extend' simplehtmldom to recognize <source>
|
||||
* tags (HTML5) as self closing tag. This patch should be removed once
|
||||
* simplehtmldom was fixed. This seems to be a issue with more tags:
|
||||
* https://sourceforge.net/p/simplehtmldom/bugs/83/
|
||||
*
|
||||
* The tag itself is valid according to Mozilla:
|
||||
*
|
||||
* The HTML <picture> element serves as a container for zero or more <source>
|
||||
* elements and one <img> element to provide versions of an image for different
|
||||
* display device scenarios. The browser will consider each of the child <source>
|
||||
* elements and select one corresponding to the best match found; if no matches
|
||||
* are found among the <source> elements, the file specified by the <img>
|
||||
* element's src attribute is selected. The selected image is then presented in
|
||||
* the space occupied by the <img> element.
|
||||
*
|
||||
* -- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture
|
||||
*
|
||||
* Notice: This class uses parts of the original simplehtmldom, adjusted to pass
|
||||
* the guidelines of RSS-Bridge (formatting)
|
||||
*/
|
||||
final class Fix_Simple_Html_Dom extends simple_html_dom {
|
||||
|
||||
/* copy from simple_html_dom, added 'source' at the end */
|
||||
protected $self_closing_tags = array(
|
||||
'img' => 1,
|
||||
'br' => 1,
|
||||
'input' => 1,
|
||||
'meta' => 1,
|
||||
'link' => 1,
|
||||
'hr' => 1,
|
||||
'base' => 1,
|
||||
'embed' => 1,
|
||||
'spacer' => 1,
|
||||
'source' => 1
|
||||
);
|
||||
|
||||
/* copy from simplehtmldom, changed 'simple_html_dom' to 'Fix_Simple_Html_Dom' */
|
||||
public static function str_get_html($str,
|
||||
$lowercase = true,
|
||||
$forceTagsClosed = true,
|
||||
$target_charset = DEFAULT_TARGET_CHARSET,
|
||||
$stripRN = true,
|
||||
$defaultBRText = DEFAULT_BR_TEXT,
|
||||
$defaultSpanText = DEFAULT_SPAN_TEXT)
|
||||
{
|
||||
$dom = new Fix_Simple_Html_Dom(null,
|
||||
$lowercase,
|
||||
$forceTagsClosed,
|
||||
$target_charset,
|
||||
$stripRN,
|
||||
$defaultBRText,
|
||||
$defaultSpanText);
|
||||
|
||||
if (empty($str) || strlen($str) > MAX_FILE_SIZE) {
|
||||
|
||||
$dom->clear();
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
$dom->load($str, $lowercase, $stripRN);
|
||||
|
||||
return $dom;
|
||||
}
|
||||
}
|
||||
|
@@ -15,8 +15,13 @@ class DansTonChatBridge extends BridgeAbstract {
|
||||
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;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ class DauphineLibereBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'qwertygc';
|
||||
const NAME = 'Dauphine Bridge';
|
||||
const URI = 'http://www.ledauphine.com/';
|
||||
const URI = 'https://www.ledauphine.com/';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
@@ -49,8 +49,9 @@ class DauphineLibereBridge extends FeedExpander {
|
||||
|
||||
private function extractContent($url){
|
||||
$html2 = getSimpleHTMLDOMCached($url);
|
||||
$text = $html2->find('div.column', 0)->innertext;
|
||||
$text = preg_replace('@<script[^>]*?>.*?</script>@si', '', $text);
|
||||
return $text;
|
||||
foreach ($html2->find('.noprint, link, script, iframe, .shareTool, .contentInfo') as $remove) {
|
||||
$remove->outertext = '';
|
||||
}
|
||||
return $html2->find('div.content', 0)->innertext;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -35,11 +35,11 @@ class DemoBridge extends BridgeAbstract {
|
||||
public function collectData(){
|
||||
|
||||
$item = array();
|
||||
$item['author'] = "Me!";
|
||||
$item['title'] = "Test";
|
||||
$item['content'] = "Awesome content !";
|
||||
$item['id'] = "Lalala";
|
||||
$item['uri'] = "http://example.com/test";
|
||||
$item['author'] = 'Me!';
|
||||
$item['title'] = 'Test';
|
||||
$item['content'] = 'Awesome content !';
|
||||
$item['id'] = 'Lalala';
|
||||
$item['uri'] = 'http://example.com/test';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -4,143 +4,163 @@ class DemonoidBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'metaMMA';
|
||||
const NAME = 'Demonoid';
|
||||
const URI = 'https://www.demonoid.pw/';
|
||||
const DESCRIPTION = 'Returns results for the keywords (in all categories or
|
||||
a specific category). You can put several keywords separated by a semicolon
|
||||
(e.g. "one show;another show"). Searches can by done in a specific category;
|
||||
category number must be specified. (All=0, Movies=1, Music=2, TV=3, Games=4,
|
||||
Applications=5, Pictures=8, Anime=9, Comics=10, Books=11 Music Videos=8,
|
||||
Audio Books=17). User feed takes the Uploader ID number (not uploader name)
|
||||
as keyword. Uploader ID is found by clicking on uploader, clicking on
|
||||
"View this user\'s torrents", and copying the number after "uid=". An entire
|
||||
category feed is accomplished by leaving "keywords" box blank and using the
|
||||
corresponding category number.';
|
||||
const DESCRIPTION = 'Returns results from search';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
const PARAMETERS = array(array(
|
||||
'q' => array(
|
||||
'name' => 'keywords/user ID/category, separated by semicolons',
|
||||
'exampleValue' => 'first list;second list;…',
|
||||
'required' => true
|
||||
),
|
||||
'crit' => array(
|
||||
'name' => 'keywords',
|
||||
'exampleValue' => 'keyword1 keyword2…',
|
||||
'required' => true,
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'name' => 'Feed type',
|
||||
'values' => array(
|
||||
'search' => 'search',
|
||||
'category' => 'cat',
|
||||
'user' => 'usr'
|
||||
'All' => 0,
|
||||
'Movies' => 1,
|
||||
'Music' => 2,
|
||||
'TV' => 3,
|
||||
'Games' => 4,
|
||||
'Applications' => 5,
|
||||
'Pictures' => 8,
|
||||
'Anime' => 9,
|
||||
'Comics' => 10,
|
||||
'Books' => 11,
|
||||
'Audiobooks' => 17
|
||||
)
|
||||
)
|
||||
),
|
||||
'catCheck' => array(
|
||||
'type' => 'checkbox',
|
||||
'name' => 'Specify category for keyword search ?',
|
||||
),
|
||||
'cat' => array(
|
||||
'name' => 'Category number',
|
||||
),
|
||||
));
|
||||
), array(
|
||||
'catOnly' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All' => 0,
|
||||
'Movies' => 1,
|
||||
'Music' => 2,
|
||||
'TV' => 3,
|
||||
'Games' => 4,
|
||||
'Applications' => 5,
|
||||
'Pictures' => 8,
|
||||
'Anime' => 9,
|
||||
'Comics' => 10,
|
||||
'Books' => 11,
|
||||
'Audiobooks' => 17
|
||||
)
|
||||
)
|
||||
), array(
|
||||
'userid' => array(
|
||||
'name' => 'user id',
|
||||
'exampleValue' => '00000',
|
||||
'required' => true,
|
||||
'type' => 'number'
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All' => 0,
|
||||
'Movies' => 1,
|
||||
'Music' => 2,
|
||||
'TV' => 3,
|
||||
'Games' => 4,
|
||||
'Applications' => 5,
|
||||
'Pictures' => 8,
|
||||
'Anime' => 9,
|
||||
'Comics' => 10,
|
||||
'Books' => 11,
|
||||
'Audiobooks' => 17
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$catBool = $this->getInput('catCheck');
|
||||
if($catBool) {
|
||||
$catNum = $this->getInput('cat');
|
||||
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 !');
|
||||
}
|
||||
$critList = $this->getInput('crit');
|
||||
|
||||
$keywordsList = explode(';', $this->getInput('q'));
|
||||
foreach($keywordsList as $keywords) {
|
||||
switch($critList) {
|
||||
case 'search':
|
||||
if($catBool == false) {
|
||||
$html = file_get_contents(
|
||||
self::URI .
|
||||
'files/?category=0&subcategory=All&quality=All&seeded=2&external=2&query=' .
|
||||
urlencode($keywords) . #not rawurlencode so space -> '+'
|
||||
'&uid=0&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
} else {
|
||||
$html = file_get_contents(
|
||||
self::URI .
|
||||
'files/?category=' .
|
||||
rawurlencode($catNum) .
|
||||
'&subcategory=All&quality=All&seeded=2&external=2&query=' .
|
||||
urlencode($keywords) . #not rawurlencode so space -> '+'
|
||||
'&uid=0&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
}
|
||||
break;
|
||||
case 'usr':
|
||||
$html = file_get_contents(
|
||||
self::URI .
|
||||
'files/?uid=' .
|
||||
rawurlencode($keywords) .
|
||||
'&seeded=2'
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
break;
|
||||
case 'cat':
|
||||
$html = file_get_contents(
|
||||
self::URI .
|
||||
'files/?uid=0&category=' .
|
||||
rawurlencode($keywords) .
|
||||
'&subcategory=0&language=0&seeded=2&quality=0&query=&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
break;
|
||||
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;
|
||||
}
|
||||
|
||||
if(preg_match('~No torrents found~', $html)) {
|
||||
returnServerError('No result for query ' . $keywords);
|
||||
}
|
||||
|
||||
$bigTable = explode('<!-- start torrent list -->', $html)[1];
|
||||
$last50 = explode('<!-- end torrent list -->', $bigTable)[0];
|
||||
$dateChunk = explode('added_today', $last50);
|
||||
$item = array ();
|
||||
|
||||
for($block = 1;$block < count($dateChunk);$block++) {
|
||||
preg_match('~(?<=>Add).*?(?=<)~', $dateChunk[$block], $dateStr);
|
||||
$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+~', $dateStr[0], $fullDateStr);
|
||||
} 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']);
|
||||
}
|
||||
|
||||
$itemsChunk = explode('<!-- tstart -->', $dateChunk[$block]);
|
||||
|
||||
for($items = 1;$items < count($itemsChunk);$items++) {
|
||||
$item = array();
|
||||
$cols = explode('<td', $itemsChunk[$items]);
|
||||
preg_match('~(?<=href=\"/).*?(?=\")~', $cols[1], $matches);
|
||||
$item['id'] = self::URI . $matches[0];
|
||||
preg_match('~(?<=href=\").*?(?=\")~', $cols[4], $matches);
|
||||
$item['uri'] = $matches[0];
|
||||
$item['timestamp'] = $timestamp;
|
||||
preg_match('~(?<=href=\"/users/).*?(?=\")~', $cols[3], $matches);
|
||||
$item['author'] = $matches[0];
|
||||
preg_match('~(?<=/\">).*?(?=</a>)~', $cols[1], $matches);
|
||||
$item['title'] = $matches[0];
|
||||
preg_match('~(?<=green\">)\d+(?=</font>)~', $cols[8], $matches);
|
||||
$item['seeders'] = $matches[0];
|
||||
preg_match('~(?<=red\">)\d+(?=</font>)~', $cols[9], $matches);
|
||||
$item['leechers'] = $matches[0];
|
||||
preg_match('~(?<=>).*?(?=</td>)~', $cols[5], $matches);
|
||||
$item['size'] = $matches[0];
|
||||
$item['content'] = 'Uploaded by ' . $item['author']
|
||||
. ' , Size ' . $item['size']
|
||||
. '<br>seeders: '
|
||||
. $item['seeders']
|
||||
. ' | leechers: '
|
||||
. $item['leechers']
|
||||
. '<br><a href="'
|
||||
. $item['id']
|
||||
. '">info page</a>';
|
||||
if(isset($item['title']))
|
||||
$this->items[] = $item;
|
||||
}
|
||||
$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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
240
bridges/DesoutterBridge.php
Normal file
240
bridges/DesoutterBridge.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
}
|
105
bridges/DevToBridge.php
Normal file
105
bridges/DevToBridge.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?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',
|
||||
'defaultValue' => false
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@@ -75,6 +75,10 @@ class DiceBridge extends BridgeAbstract {
|
||||
),
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://assets.dice.com/techpro/img/favicons/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$uri = 'https://www.dice.com/jobs/advancedResult.html';
|
||||
$uri .= '?for_one=' . urlencode($this->getInput('for_one'));
|
||||
|
@@ -9,8 +9,8 @@ class DilbertBridge extends BridgeAbstract {
|
||||
|
||||
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) {
|
||||
|
||||
|
112
bridges/DiscogsBridge.php
Normal file
112
bridges/DiscogsBridge.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?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;
|
||||
$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;
|
||||
}
|
||||
}
|
@@ -7,6 +7,11 @@ class DribbbleBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 1800;
|
||||
const DESCRIPTION = 'Returns the newest popular shots from Dribbble.';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://cdn.dribbble.com/assets/
|
||||
favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . '/shots')
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
161
bridges/ETTVBridge.php
Normal file
161
bridges/ETTVBridge.php
Normal 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;
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class EZTVBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = "alexAubin";
|
||||
const MAINTAINER = 'alexAubin';
|
||||
const NAME = 'EZTV';
|
||||
const URI = 'https://eztv.ch/';
|
||||
const DESCRIPTION = 'Returns list of *recent* torrents for a specific show
|
||||
@@ -23,15 +23,15 @@ on EZTV. Get showID from URLs in https://eztv.ch/shows/showID/show-full-name.';
|
||||
$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);
|
||||
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'));
|
||||
$showList = explode(',', $this->getInput('i'));
|
||||
foreach($showList as $showID) {
|
||||
|
||||
// Get show page
|
||||
|
@@ -7,6 +7,11 @@ class EliteDangerousGalnetBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Returns the latest page of news from Galnet';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://community.elitedangerous.com/sites/
|
||||
EDSITE_COMM/themes/bootstrap/bootstrap_community/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
147
bridges/ElloBridge.php
Normal file
147
bridges/ElloBridge.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?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(CACHE_DIR);
|
||||
$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();
|
||||
}
|
||||
|
||||
}
|
@@ -57,6 +57,10 @@ class ElsevierBridge extends BridgeAbstract {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://cdn.elsevier.io/verona/includes/favicons/favicon-32x32.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$uri = self::URI . $this->getInput('j') . '/recent-articles/';
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
|
@@ -7,19 +7,9 @@ class EstCeQuonMetEnProdBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Should we put a website in production today? (French)';
|
||||
|
||||
public function collectData(){
|
||||
function extractFromDelimiters($string, $start, $end){
|
||||
if(strpos($string, $start) !== false) {
|
||||
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
|
||||
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
|
||||
return $section_retrieved;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request EstCeQuonMetEnProd: ' . $this->getURI());
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request EstCeQuonMetEnProd: ' . self::URI);
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $this->getURI() . '#' . date('Y-m-d');
|
||||
@@ -28,8 +18,8 @@ class EstCeQuonMetEnProdBridge extends BridgeAbstract {
|
||||
$item['timestamp'] = strtotime('today midnight');
|
||||
$item['content'] = str_replace(
|
||||
'src="/',
|
||||
'src="' . $this->getURI(),
|
||||
trim(extractFromDelimiters($html->outertext, '<body role="document">', '<br /><br />'))
|
||||
'src="' . self::URI,
|
||||
trim(extractFromDelimiters($html->outertext, '<body role="document">', '<div id="share'))
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
|
@@ -17,7 +17,7 @@ class EtsyBridge extends BridgeAbstract {
|
||||
'queryextension' => array(
|
||||
'name' => 'Query extension',
|
||||
'type' => 'text',
|
||||
'requied' => false,
|
||||
'required' => false,
|
||||
'title' => 'Insert additional query parts here
|
||||
(anything after ?search=<your search query>)',
|
||||
'exampleValue' => '&explicit=1&locationQuery=2921044'
|
||||
@@ -25,9 +25,9 @@ class EtsyBridge extends BridgeAbstract {
|
||||
'showimage' => array(
|
||||
'name' => 'Show image in content',
|
||||
'type' => 'checkbox',
|
||||
'requrired' => false,
|
||||
'required' => false,
|
||||
'title' => 'Activate to show the image in the content',
|
||||
'defaultValue' => false
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -36,26 +36,27 @@ class EtsyBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Failed to receive ' . $this->getURI());
|
||||
|
||||
$results = $html->find('div.block-grid-item');
|
||||
$results = $html->find('li.block-grid-item');
|
||||
|
||||
foreach($results as $result) {
|
||||
// Skip banner cards (ads for categories)
|
||||
if($result->find('a.banner-card'))
|
||||
if($result->find('span.ad-indicator'))
|
||||
continue;
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $result->find('a', 0)->title;
|
||||
$item['uri'] = $result->find('a', 0)->href;
|
||||
$item['author'] = $result->find('div.card-shop-name', 0)->plaintext;
|
||||
$item['author'] = $result->find('p.text-gray-lighter', 0)->plaintext;
|
||||
|
||||
$item['content'] = '<p>'
|
||||
. $result->find('div.card-price', 0)->plaintext
|
||||
. $result->find('span.currency-value', 0)->plaintext . ' '
|
||||
. $result->find('span.currency-symbol', 0)->plaintext
|
||||
. '</p><p>'
|
||||
. $result->find('div.card-title', 0)->plaintext
|
||||
. $result->find('a', 0)->title
|
||||
. '</p>';
|
||||
|
||||
$image = $result->find('img.placeholder', 0)->src;
|
||||
$image = $result->find('img.display-block', 0)->src;
|
||||
|
||||
if($this->getInput('showimage')) {
|
||||
$item['content'] .= '<img src="' . $image . '">';
|
||||
|
104
bridges/ExtremeDownloadBridge.php
Normal file
104
bridges/ExtremeDownloadBridge.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
}
|
@@ -15,24 +15,18 @@ class FB2Bridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static.xx.fbcdn.net/rsrc.php/yo/r/iRmz9lCMBD2.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
function extractFromDelimiters($string, $start, $end){
|
||||
if(strpos($string, $start) !== false) {
|
||||
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
|
||||
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
|
||||
return $section_retrieved;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Utility function for cleaning a Facebook link
|
||||
$unescape_fb_link = function($matches){
|
||||
if(is_array($matches) && count($matches) > 1) {
|
||||
$link = $matches[1];
|
||||
if(strpos($link, '/') === 0)
|
||||
$link = self::URI . $link . '"';
|
||||
$link = self::URI . substr($link, 1);
|
||||
if(strpos($link, 'facebook.com/l.php?u=') !== false)
|
||||
$link = urldecode(extractFromDelimiters($link, 'facebook.com/l.php?u=', '&'));
|
||||
return ' href="' . $link . '"';
|
||||
@@ -75,14 +69,14 @@ class FB2Bridge extends BridgeAbstract {
|
||||
if($this->getInput('u') !== null) {
|
||||
$page = 'https://touch.facebook.com/' . $this->getInput('u');
|
||||
$cookies = $this->getCookies($page);
|
||||
$pageID = $this->getPageID($page, $cookies);
|
||||
$pageInfo = $this->getPageInfos($page, $cookies);
|
||||
|
||||
if($pageID === null) {
|
||||
if($pageInfo['userId'] === null) {
|
||||
echo <<<EOD
|
||||
Unable to get the page id. You should consider getting the ID by hand, then importing it into FB2Bridge
|
||||
EOD;
|
||||
die();
|
||||
} elseif($pageID == -1) {
|
||||
} elseif($pageInfo['userId'] == -1) {
|
||||
echo <<<EOD
|
||||
This page is not accessible without being logged in.
|
||||
EOD;
|
||||
@@ -91,35 +85,47 @@ EOD;
|
||||
}
|
||||
|
||||
//Build the string for the first request
|
||||
$requestString = 'https://touch.facebook.com/pages_reaction_units/more/?page_id='
|
||||
. $pageID
|
||||
. '&cursor={"card_id"%3A"videos"%2C"has_next_page"%3Atrue}&surface=mobile_page_home&unit_count=8';
|
||||
|
||||
$fileContent = file_get_contents($requestString);
|
||||
|
||||
$articleIndex = 0;
|
||||
$maxArticle = 3;
|
||||
|
||||
$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 = $this->getInput('u');
|
||||
$author = $pageInfo['username'];
|
||||
|
||||
foreach($html->find("article") as $content) {
|
||||
foreach($html->find('article') as $content) {
|
||||
|
||||
$item = array();
|
||||
//echo $content; die();
|
||||
preg_match('/publish_time\\\":([0-9]+),/', $content->getAttribute('data-store', 0), $match);
|
||||
if(isset($match[1]))
|
||||
$timestamp = $match[1];
|
||||
else
|
||||
$timestamp = 0;
|
||||
|
||||
$item['uri'] = "http://touch.facebook.com"
|
||||
. $content->find("div[class='_52jc _5qc4 _24u0 _36xo']", 0)->find("a", 0)->getAttribute("href");
|
||||
$item['uri'] = html_entity_decode('http://touch.facebook.com'
|
||||
. $content->find("div[class='_52jc _5qc4 _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href'), ENT_QUOTES);
|
||||
|
||||
if($content->find("header", 0) !== null) {
|
||||
$content->find("header", 0)->innertext = "";
|
||||
//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 = "";
|
||||
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>');
|
||||
$content = strip_tags($content, '<a><img><i><u><br><p><h3><h4><section>');
|
||||
|
||||
//Adapt link hrefs: convert relative links into absolute links and bypass external link redirection
|
||||
$content = preg_replace_callback('/ href=\"([^"]+)\"/i', $unescape_fb_link, $content);
|
||||
@@ -132,7 +138,6 @@ EOD;
|
||||
'ajaxify',
|
||||
'tabindex',
|
||||
'class',
|
||||
'style',
|
||||
'data-[^=]*',
|
||||
'aria-[^=]*',
|
||||
'role',
|
||||
@@ -145,7 +150,36 @@ EOD;
|
||||
// "<i><u>smile emoticon</u></i>" back to ASCII emoticons eg ":)"
|
||||
$content = preg_replace_callback('/<i><u>([^ <>]+) ([^<>]+)<\/u><\/i>/i', $unescape_fb_emote, $content);
|
||||
|
||||
$item['content'] = $content;
|
||||
//Remove the "...Plus" tag
|
||||
$content = preg_replace(
|
||||
'/… (<span>|)<a href="https:\/\/www\.facebook\.com\/story\.php\?story_fbid=.*?<\/a>/m',
|
||||
'', $content, 1);
|
||||
|
||||
//Remove tracking images
|
||||
$content = preg_replace('/<img src=\'.*?safe_image\.php.*?\' \/>/m', '', $content);
|
||||
|
||||
//Remove the double section tags
|
||||
$content = str_replace(['<section><section>', '</section></section>'], ['<section>', '</section>'], $content);
|
||||
|
||||
//Move the section tag link upper, if it is down
|
||||
$content = str_get_html($content);
|
||||
$sectionContent = $content->find('section', 0);
|
||||
if($sectionContent != null) {
|
||||
$sectionLink = $sectionContent->nextSibling();
|
||||
if($sectionLink != null) {
|
||||
$fullLink = '<a href="' . $sectionLink->getAttribute('href') . '">' . $sectionContent->innertext . '</a>';
|
||||
$sectionContent->innertext = $fullLink;
|
||||
}
|
||||
}
|
||||
|
||||
//Move the href tag upper if it is inside the section
|
||||
foreach($content->find('section > a') as $sectionToFix) {
|
||||
$sectionLink = $sectionToFix->getAttribute('href');
|
||||
$section = $sectionToFix->parent();
|
||||
$section->outertext = '<a href="' . $sectionLink . '">' . $section . '</a>';
|
||||
}
|
||||
|
||||
$item['content'] = html_entity_decode($content, ENT_QUOTES);
|
||||
|
||||
$title = $author;
|
||||
if (strlen($title) > 24)
|
||||
@@ -154,57 +188,29 @@ EOD;
|
||||
if (strlen($title) > 64)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
|
||||
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['title'] = html_entity_decode($title, ENT_QUOTES);
|
||||
$item['author'] = html_entity_decode($author, ENT_QUOTES);
|
||||
$item['timestamp'] = html_entity_decode($timestamp, ENT_QUOTES);
|
||||
|
||||
array_push($this->items, $item);
|
||||
if($item['timestamp'] != 0)
|
||||
array_push($this->items, $item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Currently not used. Is used to get more than only 3 elements, as they appear on another page.
|
||||
private function computeNextLink($string, $pageID){
|
||||
|
||||
$regex = implode(
|
||||
'',
|
||||
array(
|
||||
"/timeline_unit",
|
||||
"\\\\\\\\u00253A1",
|
||||
"\\\\\\\\u00253A([0-9]*)",
|
||||
"\\\\\\\\u00253A([0-9]*)",
|
||||
"\\\\\\\\u00253A([0-9]*)",
|
||||
"\\\\\\\\u00253A([0-9]*)/"
|
||||
)
|
||||
);
|
||||
|
||||
preg_match($regex, $string, $result);
|
||||
|
||||
return implode(
|
||||
'',
|
||||
array(
|
||||
"https://touch.facebook.com/pages_reaction_units/more/?page_id=",
|
||||
$pageID,
|
||||
"&cursor=%7B%22timeline_cursor%22%3A%22timeline_unit%3A1%3A",
|
||||
$result[1],
|
||||
"%3A",
|
||||
$result[2],
|
||||
"%3A",
|
||||
$result[3],
|
||||
"%3A",
|
||||
$result[4],
|
||||
"%22%2C%22timeline_section_cursor%22%3A%7B%7D%2C%22",
|
||||
"has_next_page%22%3Atrue%7D&surface=mobile_page_home&unit_count=3"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
//Builds the HTML from the encoded JS that Facebook provides.
|
||||
private function buildContent($pageContent){
|
||||
|
||||
$regex = "/\\\"html\\\":\\\"(.*?)\\\",\\\"replace/";
|
||||
// The html ends with:
|
||||
// /div>","replaceifexists
|
||||
$regex = '/\\"html\\":(\".+\/div>"),"replace/';
|
||||
preg_match($regex, $pageContent, $result);
|
||||
|
||||
return str_get_html(html_entity_decode(json_decode('"' . $result[1] . '"')));
|
||||
$htmlContent = json_decode($result[1]);
|
||||
$htmlContent = preg_replace('/(?<!style)="(.*?)"/', '=\'$1\'', $htmlContent);
|
||||
$htmlContent = html_entity_decode($htmlContent, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return str_get_html($htmlContent);
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +220,7 @@ EOD;
|
||||
|
||||
$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",
|
||||
'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'
|
||||
)
|
||||
)
|
||||
@@ -222,24 +228,24 @@ EOD;
|
||||
$a = file_get_contents($pageURL, 0, $ctx);
|
||||
|
||||
//First request to get the cookie
|
||||
$cookies = "";
|
||||
$cookies = '';
|
||||
foreach($http_response_header as $hdr) {
|
||||
if(strpos($hdr, "Set-Cookie") !== false) {
|
||||
$cLine = explode(":", $hdr)[1];
|
||||
$cLine = explode(";", $cLine)[0];
|
||||
$cookies .= ";" . $cLine;
|
||||
if(strpos($hdr, 'Set-Cookie') !== false) {
|
||||
$cLine = explode(':', $hdr)[1];
|
||||
$cLine = explode(';', $cLine)[0];
|
||||
$cookies .= ';' . $cLine;
|
||||
}
|
||||
}
|
||||
|
||||
return substr($cookies, 1);
|
||||
}
|
||||
|
||||
//Get the page ID from the Facebook page.
|
||||
private function getPageID($page, $cookies){
|
||||
//Get the page ID and username from the Facebook page.
|
||||
private function getPageInfos($page, $cookies){
|
||||
|
||||
$context = stream_context_create(array(
|
||||
'http' => array(
|
||||
'user_agent' => "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0",
|
||||
'user_agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0',
|
||||
'header' => 'Cookie: ' . $cookies
|
||||
)
|
||||
)
|
||||
@@ -247,23 +253,32 @@ EOD;
|
||||
|
||||
$pageContent = file_get_contents($page, 0, $context);
|
||||
|
||||
if(strpos($pageContent, "signup-button") != false) {
|
||||
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]*)&/";
|
||||
$regex = '/page_id=([0-9]*)&/';
|
||||
preg_match($regex, $pageContent, $matches);
|
||||
|
||||
if(count($matches) > 0) {
|
||||
return $matches[1];
|
||||
return array('userId' => $matches[1], 'username' => $username);
|
||||
}
|
||||
|
||||
//Get the page ID if we do have a captcha
|
||||
$regex = "/\"pageID\":\"([0-9]*)\"/";
|
||||
$regex = '/"pageID":"([0-9]*)"/';
|
||||
preg_match($regex, $pageContent, $matches);
|
||||
|
||||
return $matches[1];
|
||||
return array('userId' => $matches[1], 'username' => $username);
|
||||
|
||||
}
|
||||
|
||||
@@ -275,7 +290,4 @@ EOD;
|
||||
return 'http://facebook.com';
|
||||
}
|
||||
|
||||
public function getCacheDuration(){
|
||||
return 60 * 60 * 3; // 5 minutes
|
||||
}
|
||||
}
|
||||
|
58
bridges/FDroidBridge.php
Normal file
58
bridges/FDroidBridge.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,190 +1,527 @@
|
||||
<?php
|
||||
class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'Facebook';
|
||||
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 PARAMETERS = array( 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'
|
||||
const PARAMETERS = array(
|
||||
'User' => array(
|
||||
'u' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true
|
||||
),
|
||||
'defaultValue' => 'all'
|
||||
'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 $groupName = '';
|
||||
|
||||
public function collectData(){
|
||||
public function getIcon() {
|
||||
return 'https://static.xx.fbcdn.net/rsrc.php/yo/r/iRmz9lCMBD2.ico';
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
public function getName(){
|
||||
|
||||
switch($this->queriedContext) {
|
||||
|
||||
case 'User':
|
||||
if(!empty($this->authorName)) {
|
||||
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName
|
||||
. ' - ' . static::NAME;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Group':
|
||||
if(!empty($this->groupName)) {
|
||||
return $this->groupName . ' - ' . static::NAME;
|
||||
}
|
||||
break;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Utility function for cleaning a Facebook link
|
||||
$unescape_fb_link = function($matches){
|
||||
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_HOST_REQUIRED | 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']
|
||||
. '"!');
|
||||
|
||||
}
|
||||
|
||||
return explode('/', $urlparts['path'])[2];
|
||||
|
||||
} elseif(strpos($group, '/') !== false) {
|
||||
returnClientError('The group you provided is invalid: ' . $group);
|
||||
} else {
|
||||
return $group;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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, '/') === 0)
|
||||
$link = self::URI . $link;
|
||||
|
||||
if(strpos($link, 'facebook.com/l.php?u=') !== false)
|
||||
$link = urldecode(extractFromDelimiters($link, 'facebook.com/l.php?u=', '&'));
|
||||
|
||||
return ' href="' . $link . '"';
|
||||
|
||||
}
|
||||
};
|
||||
}, $content);
|
||||
}
|
||||
|
||||
//Utility function for converting facebook emoticons
|
||||
$unescape_fb_emote = function($matches){
|
||||
static $facebook_emoticons = array(
|
||||
'smile' => ':)',
|
||||
'frown' => ':(',
|
||||
'tongue' => ':P',
|
||||
'grin' => ':D',
|
||||
'gasp' => ':O',
|
||||
'wink' => ';)',
|
||||
'pacman' => ':<',
|
||||
'grumpy' => '>_<',
|
||||
'unsure' => ':/',
|
||||
'cry' => ':\'(',
|
||||
'kiki' => '^_^',
|
||||
'glasses' => '8-)',
|
||||
'sunglasses' => 'B-)',
|
||||
'heart' => '<3',
|
||||
'devil' => ']:D',
|
||||
'angel' => '0:)',
|
||||
'squint' => '-_-',
|
||||
'confused' => 'o_O',
|
||||
'upset' => 'xD',
|
||||
'colonthree' => ':3',
|
||||
'like' => '👍');
|
||||
$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];
|
||||
};
|
||||
/**
|
||||
* 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' => '👍');
|
||||
|
||||
$html = null;
|
||||
$len = count($matches);
|
||||
|
||||
//Handle captcha response sent by the viewer
|
||||
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));
|
||||
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/html');
|
||||
|
||||
$message = <<<EOD
|
||||
<form method="post" action="?{$_SERVER['QUERY_STRING']}">
|
||||
<h2>Facebook captcha challenge</h2>
|
||||
<p>Unfortunately, rss-bridge cannot fetch the requested page.<br />
|
||||
Facebook wants rss-bridge to resolve the following captcha:</p>
|
||||
<p><img src="data:image/png;base64,{$img}" /></p>
|
||||
<p><b>Response:</b> <input name="captcha_response" placeholder="please fill in" />
|
||||
<input type="submit" value="Submit!" /></p>
|
||||
</form>
|
||||
EOD;
|
||||
|
||||
die($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a capture response was received and tries to load the contents
|
||||
* @return mixed null if no capture response was received, simplhtmldom document otherwise
|
||||
*/
|
||||
private function handleCaptchaResponse() {
|
||||
if (isset($_POST['captcha_response'])) {
|
||||
if (session_status() == PHP_SESSION_NONE)
|
||||
session_start();
|
||||
|
||||
if (isset($_SESSION['captcha_fields'], $_SESSION['captcha_action'])) {
|
||||
$captcha_action = $_SESSION['captcha_action'];
|
||||
$captcha_fields = $_SESSION['captcha_fields'];
|
||||
$captcha_fields['captcha_response'] = preg_replace("/[^a-zA-Z0-9]+/", "", $_POST['captcha_response']);
|
||||
$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)
|
||||
),
|
||||
);
|
||||
$context = stream_context_create($http_options);
|
||||
$html = getContents($captcha_action, false, $context);
|
||||
$captcha_fields['captcha_response'] = preg_replace('/[^a-zA-Z0-9]+/', '', $_POST['captcha_response']);
|
||||
|
||||
if($html === false) {
|
||||
returnServerError('Failed to submit captcha response back to Facebook');
|
||||
}
|
||||
unset($_SESSION['captcha_fields']);
|
||||
$html = str_get_html($html);
|
||||
$header = array(
|
||||
'Content-type: application/x-www-form-urlencoded',
|
||||
'Referer: ' . $captcha_action,
|
||||
'Cookie: noscript=1'
|
||||
);
|
||||
|
||||
$opts = array(
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_POSTFIELDS => http_build_query($captcha_fields)
|
||||
);
|
||||
|
||||
$html = 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
|
||||
return null;
|
||||
}
|
||||
|
||||
private function collectUserData(){
|
||||
|
||||
$html = $this->handleCaptchaResponse();
|
||||
|
||||
// Retrieve page contents
|
||||
if(is_null($html)) {
|
||||
$http_options = array(
|
||||
'http' => array(
|
||||
'method' => 'GET',
|
||||
'user_agent' => ini_get('user_agent'),
|
||||
'header' => 'Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n"
|
||||
)
|
||||
);
|
||||
$context = stream_context_create($http_options);
|
||||
if(!strpos($this->getInput('u'), "/")) {
|
||||
$html = getSimpleHTMLDOM(self::URI . urlencode($this->getInput('u')) . '?_fb_noscript=1',
|
||||
false,
|
||||
$context)
|
||||
or returnServerError('No results for this query.');
|
||||
} else {
|
||||
$html = getSimpleHTMLDOM(self::URI . 'pages/' . $this->getInput('u') . '?_fb_noscript=1',
|
||||
false,
|
||||
$context)
|
||||
or returnServerError('No results for this query.');
|
||||
}
|
||||
|
||||
$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'] = $captcha->find('form', 0)->action;
|
||||
|
||||
//Show captcha filling form to the viewer, proxying the captcha image
|
||||
$img = base64_encode(getContents($captcha->find('img', 0)->src));
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/html');
|
||||
$message = <<<EOD
|
||||
<form method="post" action="?{$_SERVER['QUERY_STRING']}">
|
||||
<h2>Facebook captcha challenge</h2>
|
||||
<p>Unfortunately, rss-bridge cannot fetch the requested page.<br />
|
||||
Facebook wants rss-bridge to resolve the following captcha:</p>
|
||||
<p><img src="data:image/png;base64,{$img}" /></p>
|
||||
<p><b>Response:</b> <input name="captcha_response" placeholder="please fill in" />
|
||||
<input type="submit" value="Submit!" /></p>
|
||||
</form>
|
||||
EOD;
|
||||
die($message);
|
||||
if (!is_null($captcha)) {
|
||||
$this->returnCaptchaMessage($captcha);
|
||||
}
|
||||
|
||||
// No captcha? We can carry on retrieving page contents :)
|
||||
// First, we check wether the page is public or not
|
||||
$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.');
|
||||
}
|
||||
|
||||
//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);
|
||||
|
||||
if(isset($element)) {
|
||||
|
||||
$author = str_replace(' | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
|
||||
$profilePic = 'https://graph.facebook.com/'
|
||||
. $this->getInput('u')
|
||||
. '/picture?width=200&height=200';
|
||||
|
||||
$profilePic = $html->find('meta[property="og:image"]', 0)->content;
|
||||
|
||||
$this->authorName = $author;
|
||||
|
||||
@@ -196,6 +533,12 @@ EOD;
|
||||
$posts = array($cell);
|
||||
}
|
||||
|
||||
// Optionally skip reviews
|
||||
if($this->getInput('skip_reviews')
|
||||
&& !is_null($cell->find('#review_composer_container', 0))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach($posts as $post) {
|
||||
// Check media type
|
||||
switch($this->getInput('media_type')) {
|
||||
@@ -213,11 +556,7 @@ EOD;
|
||||
|
||||
if(count($post->find('abbr')) > 0) {
|
||||
|
||||
//Retrieve post contents
|
||||
$content = preg_replace(
|
||||
'/(?i)><div class=\"clearfix([^>]+)>(.+?)div\ class=\"userContent\"/i',
|
||||
'',
|
||||
$post);
|
||||
$content = $post->find('.userContentWrapper', 0);
|
||||
|
||||
$content = preg_replace(
|
||||
'/(?i)><div class=\"_59tj([^>]+)>(.+?)<\/div><\/div><a/i',
|
||||
@@ -234,13 +573,18 @@ EOD;
|
||||
'',
|
||||
$content);
|
||||
|
||||
//Remove html nodes, keep only img, links, basic formatting
|
||||
// Remove "SpSonsSoriSsés"
|
||||
$content = preg_replace(
|
||||
'/(?iU)<a [^>]+ href="#" role="link" [^>}]+>.+<\/a>/iU',
|
||||
'',
|
||||
$content);
|
||||
|
||||
// Remove html nodes, keep only img, links, basic formatting
|
||||
$content = strip_tags($content, '<a><img><i><u><br><p>');
|
||||
|
||||
//Adapt link hrefs: convert relative links into absolute links and bypass external link redirection
|
||||
$content = preg_replace_callback('/ href=\"([^"]+)\"/i', $unescape_fb_link, $content);
|
||||
$content = $this->unescape_fb_link($content);
|
||||
|
||||
//Clean useless html tag properties and fix link closing tags
|
||||
// Clean useless html tag properties and fix link closing tags
|
||||
foreach (array(
|
||||
'onmouseover',
|
||||
'onclick',
|
||||
@@ -253,35 +597,45 @@ EOD;
|
||||
'aria-[^=]*',
|
||||
'role',
|
||||
'rel',
|
||||
'id') as $property_name)
|
||||
$content = preg_replace('/ ' . $property_name . '=\"[^"]*\"/i', '', $content);
|
||||
'id') as $property_name) {
|
||||
$content = preg_replace('/ ' . $property_name . '=\"[^"]*\"/i', '', $content);
|
||||
}
|
||||
|
||||
$content = preg_replace('/<\/a [^>]+>/i', '</a>', $content);
|
||||
|
||||
//Convert textual representation of emoticons eg
|
||||
//"<i><u>smile emoticon</u></i>" back to ASCII emoticons eg ":)"
|
||||
$content = preg_replace_callback(
|
||||
'/<i><u>([^ <>]+) ([^<>]+)<\/u><\/i>/i',
|
||||
$unescape_fb_emote,
|
||||
$content
|
||||
);
|
||||
$this->unescape_fb_emote($content);
|
||||
|
||||
// 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);
|
||||
|
||||
// Retrieve date of the post
|
||||
$date = $post->find('abbr')[0];
|
||||
|
||||
//Retrieve date of the post
|
||||
$date = $post->find("abbr")[0];
|
||||
if(isset($date) && $date->hasAttribute('data-utime')) {
|
||||
$date = $date->getAttribute('data-utime');
|
||||
} else {
|
||||
$date = 0;
|
||||
}
|
||||
|
||||
//Build title from username and content
|
||||
// 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")) . '...';
|
||||
|
||||
$uri = self::URI . $post->find('abbr')[0]->parent()->getAttribute('href');
|
||||
$uri = $post->find('abbr')[0]->parent()->getAttribute('href');
|
||||
|
||||
if (false !== strpos($uri, '?')) {
|
||||
$uri = substr($uri, 0, strpos($uri, '?'));
|
||||
}
|
||||
|
||||
//Build and add final item
|
||||
$item['uri'] = htmlspecialchars_decode($uri);
|
||||
@@ -289,6 +643,11 @@ EOD;
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['timestamp'] = $date;
|
||||
|
||||
if(strpos($item['content'], '<img') === false) {
|
||||
$item['enclosures'] = array($profilePic);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
@@ -296,12 +655,6 @@ EOD;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!empty($this->authorName)) {
|
||||
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName
|
||||
. ' - Facebook Bridge';
|
||||
}
|
||||
#endregion (User)
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
@@ -7,18 +7,27 @@ class FierPandaBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Returns latest articles from Fier Panda.';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'wp-content/themes/fier-panda/img/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request Fier Panda.');
|
||||
|
||||
foreach($html->find('div.container-content article') as $element) {
|
||||
defaultLinkTo($html, static::URI);
|
||||
|
||||
foreach($html->find('article') as $article) {
|
||||
|
||||
$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;
|
||||
|
||||
$item['uri'] = $article->find('a', 0)->href;
|
||||
$item['title'] = $article->find('a', 0)->title;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ class FilterBridge extends FeedExpander {
|
||||
const NAME = 'Filter';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Filters a feed of your choice';
|
||||
const URI = 'https://github.com/rss-bridge/rss-bridge';
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
'url' => array(
|
||||
@@ -26,11 +27,34 @@ class FilterBridge extends FeedExpander {
|
||||
),
|
||||
'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'])) {
|
||||
|
@@ -30,30 +30,76 @@ class FlickrBridge extends BridgeAbstract {
|
||||
'title' => 'Insert username (as shown in the address bar)',
|
||||
'exampleValue' => 'flickr'
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
|
||||
switch($this->queriedContext) {
|
||||
|
||||
case 'Explore':
|
||||
$key = 'photos';
|
||||
$filter = 'photo-lite-models';
|
||||
$html = getSimpleHTMLDOM(self::URI . 'explore')
|
||||
or returnServerError('Could not request Flickr.');
|
||||
break;
|
||||
|
||||
case 'By keyword':
|
||||
$key = 'photos';
|
||||
$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':
|
||||
$key = 'photoPageList';
|
||||
$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;
|
||||
@@ -62,59 +108,79 @@ class FlickrBridge extends BridgeAbstract {
|
||||
$start = strpos($model_text, 'modelExport:') + strlen('modelExport:');
|
||||
$end = strpos($model_text, 'auth:') - strlen('auth:');
|
||||
|
||||
// Dissect JSON data and remove trailing comma
|
||||
// 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);
|
||||
|
||||
$model_json = json_decode($model_text, true);
|
||||
return json_decode($model_text, true);
|
||||
|
||||
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);
|
||||
|
||||
// Use JSON data to build items
|
||||
foreach(reset($model_json)[0][$key]['_data'] as $element) {
|
||||
if($element['id'] === $imageID) {
|
||||
$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', $element)) {
|
||||
$item['author'] = $element['username'];
|
||||
} elseif (array_key_exists('owner', reset($model_json)[0])) {
|
||||
$item['author'] = reset($model_json)[0]['owner']['username'];
|
||||
}
|
||||
|
||||
$item['title'] = (array_key_exists('title', $element) ? $element['title'] : 'Untitled');
|
||||
$item['uri'] = self::URI . 'photo.gne?id=' . $imageID;
|
||||
|
||||
$description = (array_key_exists('description', $element) ? $element['description'] : '');
|
||||
|
||||
$item['content'] = '<a href="'
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. $imageURI
|
||||
. '" /></a><br><p>'
|
||||
. $description
|
||||
. '</p>';
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -15,47 +15,47 @@ class FootitoBridge extends BridgeAbstract {
|
||||
|
||||
$content = trim($element->innertext);
|
||||
$content = str_replace(
|
||||
"<img",
|
||||
'<img',
|
||||
"<img style='float : left;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"logo\"",
|
||||
'class="logo"',
|
||||
"style='float : left;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"contenu\"",
|
||||
'class="contenu"',
|
||||
"style='margin-left : 60px;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"responsive-comment\"",
|
||||
'class="responsive-comment"',
|
||||
"style='border-top : 1px #DDD solid; background-color : white; padding : 10px;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"jaime\"",
|
||||
'class="jaime"',
|
||||
"style='display : none;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"auteur-event responsive\"",
|
||||
'class="auteur-event responsive"',
|
||||
"style='display : none;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"report-abuse-button\"",
|
||||
'class="report-abuse-button"',
|
||||
"style='display : none;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"reaction clearfix\"",
|
||||
'class="reaction clearfix"',
|
||||
"style='margin : 10px 0px; padding : 5px; border-bottom : 1px #DDD solid;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"infos\"",
|
||||
'class="infos"',
|
||||
"style='font-size : 0.7em;'",
|
||||
$content );
|
||||
|
||||
|
41
bridges/ForGifsBridge.php
Normal file
41
bridges/ForGifsBridge.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -30,7 +30,7 @@ class FourchanBridge extends BridgeAbstract {
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError("Could not request 4chan, thread not found");
|
||||
or returnServerError('Could not request 4chan, thread not found');
|
||||
|
||||
foreach($html->find('div.postContainer') as $element) {
|
||||
$item = array();
|
||||
@@ -69,7 +69,7 @@ class FourchanBridge extends BridgeAbstract {
|
||||
. '" src="'
|
||||
. $item['imageThumb']
|
||||
. '" /></a><br>'
|
||||
.$item['content'];
|
||||
. $item['content'];
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ class FuturaSciencesBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'Futura-Sciences Bridge';
|
||||
const URI = 'http://www.futura-sciences.com/';
|
||||
const URI = 'https://www.futura-sciences.com/';
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
@@ -90,42 +90,11 @@ class FuturaSciencesBridge extends FeedExpander {
|
||||
or returnServerError('Could not request Futura-Sciences: ' . $item['uri']);
|
||||
$item['content'] = $this->extractArticleContent($article);
|
||||
$author = $this->extractAuthor($article);
|
||||
$item['author'] = empty($author) ? $item['author'] : $author;
|
||||
if (!empty($author))
|
||||
$item['author'] = $author;
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function stripWithDelimiters($string, $start, $end){
|
||||
while(strpos($string, $start) !== false) {
|
||||
$section_to_remove = substr($string, strpos($string, $start));
|
||||
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
|
||||
$string = str_replace($section_to_remove, '', $string);
|
||||
} return $string;
|
||||
}
|
||||
|
||||
private function stripRecursiveHTMLSection($string, $tag_name, $tag_start){
|
||||
$open_tag = '<' . $tag_name;
|
||||
$close_tag = '</' . $tag_name . '>';
|
||||
$close_tag_length = strlen($close_tag);
|
||||
if(strpos($tag_start, $open_tag) === 0) {
|
||||
while(strpos($string, $tag_start) !== false) {
|
||||
$max_recursion = 100;
|
||||
$section_to_remove = null;
|
||||
$section_start = strpos($string, $tag_start);
|
||||
$search_offset = $section_start;
|
||||
do {
|
||||
$max_recursion--;
|
||||
$section_end = strpos($string, $close_tag, $search_offset);
|
||||
$search_offset = $section_end + $close_tag_length;
|
||||
$section_to_remove = substr($string, $section_start, $section_end - $section_start + $close_tag_length);
|
||||
$open_tag_count = substr_count($section_to_remove, $open_tag);
|
||||
$close_tag_count = substr_count($section_to_remove, $close_tag);
|
||||
} while ($open_tag_count > $close_tag_count && $max_recursion > 0);
|
||||
$string = str_replace($section_to_remove, '', $string);
|
||||
}
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
private function extractArticleContent($article){
|
||||
$contents = $article->find('section.article-text-classic', 0)->innertext;
|
||||
$headline = trim($article->find('p.description', 0)->plaintext);
|
||||
@@ -137,6 +106,7 @@ class FuturaSciencesBridge extends FeedExpander {
|
||||
'<div class="sharebar2',
|
||||
'<div class="diaporamafullscreen"',
|
||||
'<div class="module social-button',
|
||||
'<div class="module social-share',
|
||||
'<div style="margin-bottom:10px;" class="noprint"',
|
||||
'<div class="ficheprevnext',
|
||||
'<div class="bar noprint',
|
||||
@@ -148,16 +118,17 @@ class FuturaSciencesBridge extends FeedExpander {
|
||||
'<div id="forumcomments',
|
||||
'<div ng-if="active"'
|
||||
) as $div_start) {
|
||||
$contents = $this->stripRecursiveHTMLSection($contents, 'div', $div_start);
|
||||
$contents = stripRecursiveHTMLSection($contents, 'div', $div_start);
|
||||
}
|
||||
|
||||
$contents = $this->stripWithDelimiters($contents, '<hr ', '/>');
|
||||
$contents = $this->stripWithDelimiters($contents, '<p class="content-date', '</p>');
|
||||
$contents = $this->stripWithDelimiters($contents, '<h1 class="content-title', '</h1>');
|
||||
$contents = $this->stripWithDelimiters($contents, 'fs:definition="', '"');
|
||||
$contents = $this->stripWithDelimiters($contents, 'fs:xt:clicktype="', '"');
|
||||
$contents = $this->stripWithDelimiters($contents, 'fs:xt:clickname="', '"');
|
||||
$contents = $this->stripWithDelimiters($contents, '<script ', '</script>');
|
||||
$contents = stripWithDelimiters($contents, '<hr ', '/>');
|
||||
$contents = stripWithDelimiters($contents, '<p class="content-date', '</p>');
|
||||
$contents = stripWithDelimiters($contents, '<h1 class="content-title', '</h1>');
|
||||
$contents = stripWithDelimiters($contents, 'fs:definition="', '"');
|
||||
$contents = stripWithDelimiters($contents, 'fs:xt:clicktype="', '"');
|
||||
$contents = stripWithDelimiters($contents, 'fs:xt:clickname="', '"');
|
||||
$contents = StripWithDelimiters($contents, '<section class="module-toretain module-propal-nl', '</section>');
|
||||
$contents = stripWithDelimiters($contents, '<script ', '</script>');
|
||||
|
||||
return $headline . trim($contents);
|
||||
}
|
||||
|
@@ -20,50 +20,58 @@ class GBAtempBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
private function extractFromDelimiters($string, $start, $end){
|
||||
if(strpos($string, $start) !== false) {
|
||||
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
|
||||
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
|
||||
return $section_retrieved;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function stripWithDelimiters($string, $start, $end){
|
||||
while(strpos($string, $start) !== false) {
|
||||
$section_to_remove = substr($string, strpos($string, $start));
|
||||
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
|
||||
$string = str_replace($section_to_remove, '', $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
private function buildItem($uri, $title, $author, $timestamp, $content){
|
||||
private function buildItem($uri, $title, $author, $timestamp, $thumbnail, $content){
|
||||
$item = array();
|
||||
$item['uri'] = $uri;
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['content'] = $content;
|
||||
if (!empty($thumbnail)) {
|
||||
$item['enclosures'] = array($thumbnail);
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function cleanupPostContent($content, $site_url){
|
||||
$content = str_replace(':arrow:', '➤', $content);
|
||||
$content = str_replace('href="attachments/', 'href="'.$site_url.'attachments/', $content);
|
||||
$content = $this->stripWithDelimiters($content, '<script', '</script>');
|
||||
$content = str_replace('href="attachments/', 'href="' . $site_url . 'attachments/', $content);
|
||||
$content = stripWithDelimiters($content, '<script', '</script>');
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function findItemDate($item){
|
||||
$time = 0;
|
||||
$dateField = $item->find('abbr.DateTime', 0);
|
||||
if (is_object($dateField)) {
|
||||
$time = intval(
|
||||
extractFromDelimiters(
|
||||
$dateField->outertext,
|
||||
'data-time="',
|
||||
'"'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$dateField = $item->find('span.DateTime', 0);
|
||||
$time = DateTime::createFromFormat(
|
||||
'M j, Y \a\t g:i A',
|
||||
extractFromDelimiters(
|
||||
$dateField->outertext,
|
||||
'title="',
|
||||
'"'
|
||||
)
|
||||
)->getTimestamp();
|
||||
}
|
||||
return $time;
|
||||
}
|
||||
|
||||
private function fetchPostContent($uri, $site_url){
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
$html = getSimpleHTMLDOMCached($uri);
|
||||
if(!$html) {
|
||||
return 'Could not request GBAtemp ' . $uri;
|
||||
return 'Could not request GBAtemp: ' . $uri;
|
||||
}
|
||||
|
||||
$content = $html->find('div.messageContent', 0)->innertext;
|
||||
$content = $html->find('div.messageContent, blockquote.baseHtml', 0)->innertext;
|
||||
return $this->cleanupPostContent($content, $site_url);
|
||||
}
|
||||
|
||||
@@ -76,70 +84,56 @@ class GBAtempBridge extends BridgeAbstract {
|
||||
case 'N':
|
||||
foreach($html->find('li[class=news_item full]') as $newsItem) {
|
||||
$url = self::URI . $newsItem->find('a', 0)->href;
|
||||
$time = intval(
|
||||
$this->extractFromDelimiters(
|
||||
$newsItem->find('abbr.DateTime', 0)->outertext,
|
||||
'data-time="',
|
||||
'"'
|
||||
)
|
||||
);
|
||||
$img = $this->getURI() . $newsItem->find('img', 0)->src . '#.image';
|
||||
$time = $this->findItemDate($newsItem);
|
||||
$author = $newsItem->find('a.username', 0)->plaintext;
|
||||
$title = $newsItem->find('a', 1)->plaintext;
|
||||
$content = $this->fetchPostContent($url, self::URI);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $content);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $img, $content);
|
||||
unset($newsItem); // Some items are heavy, freeing the item proactively helps saving memory
|
||||
}
|
||||
break;
|
||||
case 'R':
|
||||
foreach($html->find('li.portal_review') as $reviewItem) {
|
||||
$url = self::URI . $reviewItem->find('a', 0)->href;
|
||||
$img = $this->getURI() . extractFromDelimiters($reviewItem->find('a', 0)->style, 'image:url(', ')');
|
||||
$title = $reviewItem->find('span.review_title', 0)->plaintext;
|
||||
$content = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request GBAtemp: ' . $uri);
|
||||
$author = $content->find('a.username', 0)->plaintext;
|
||||
$time = intval(
|
||||
$this->extractFromDelimiters(
|
||||
$content->find('abbr.DateTime', 0)->outertext,
|
||||
'data-time="',
|
||||
'"'
|
||||
)
|
||||
);
|
||||
$time = $this->findItemDate($content);
|
||||
$intro = '<p><b>' . ($content->find('div#review_intro', 0)->plaintext) . '</b></p>';
|
||||
$review = $content->find('div#review_main', 0)->innertext;
|
||||
$subheader = '<p><b>' . $content->find('div.review_subheader', 0)->plaintext . '</b></p>';
|
||||
$procons = $content->find('table.review_procons', 0)->outertext;
|
||||
$scores = $content->find('table.reviewscores', 0)->outertext;
|
||||
$content = $this->cleanupPostContent($intro . $review . $subheader . $procons . $scores, self::URI);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $content);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $img, $content);
|
||||
unset($reviewItem); // Free up memory
|
||||
}
|
||||
break;
|
||||
case 'T':
|
||||
foreach($html->find('li.portal-tutorial') as $tutorialItem) {
|
||||
$url = self::URI . $tutorialItem->find('a', 0)->href;
|
||||
$title = $tutorialItem->find('a', 0)->plaintext;
|
||||
$time = intval(
|
||||
$this->extractFromDelimiters(
|
||||
$tutorialItem->find('abbr.DateTime', 0)->outertext,
|
||||
'data-time="',
|
||||
'"'
|
||||
)
|
||||
);
|
||||
$time = $this->findItemDate($tutorialItem);
|
||||
$author = $tutorialItem->find('a.username', 0)->plaintext;
|
||||
$content = $this->fetchPostContent($url, self::URI);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $content);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, null, $content);
|
||||
unset($tutorialItem); // Free up memory
|
||||
}
|
||||
break;
|
||||
case 'F':
|
||||
foreach($html->find('li.rc_item') as $postItem) {
|
||||
$url = self::URI . $postItem->find('a', 1)->href;
|
||||
$title = $postItem->find('a', 1)->plaintext;
|
||||
$time = intval(
|
||||
$this->extractFromDelimiters(
|
||||
$postItem->find('abbr.DateTime', 0)->outertext,
|
||||
'data-time="',
|
||||
'"'
|
||||
)
|
||||
);
|
||||
$time = $this->findItemDate($postItem);
|
||||
$author = $postItem->find('a.username', 0)->plaintext;
|
||||
$content = $this->fetchPostContent($url, self::URI);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $content);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, null, $content);
|
||||
unset($postItem); // Free up memory
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
66
bridges/GOGBridge.php
Normal file
66
bridges/GOGBridge.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?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;
|
||||
|
||||
}
|
||||
|
||||
}
|
119
bridges/GQMagazineBridge.php
Normal file
119
bridges/GQMagazineBridge.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?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 PARAMETERS = array( array(
|
||||
'domain' => array(
|
||||
'name' => 'Domain to use',
|
||||
'required' => true,
|
||||
'values' => array(
|
||||
'www.gqmagazine.fr' => 'www.gqmagazine.fr'
|
||||
),
|
||||
'defaultValue' => 'www.gqmagazine.fr'
|
||||
),
|
||||
'page' => array(
|
||||
'name' => 'Initial page to load',
|
||||
'required' => true
|
||||
),
|
||||
));
|
||||
|
||||
const REPLACED_ATTRIBUTES = array(
|
||||
'href' => 'href',
|
||||
'src' => 'src',
|
||||
'data-original' => 'src'
|
||||
);
|
||||
|
||||
private function getDomain() {
|
||||
return $this->getInput('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;
|
||||
}
|
||||
}
|
164
bridges/GitHubGistBridge.php
Normal file
164
bridges/GitHubGistBridge.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -106,7 +106,7 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
$content = $comment->parent()->innertext;
|
||||
} else {
|
||||
$title .= ' / ' . trim($comment->firstChild()->plaintext);
|
||||
$content = "<pre>" . $comment->find('.comment-body', 0)->innertext . "</pre>";
|
||||
$content = '<pre>' . $comment->find('.comment-body', 0)->innertext . '</pre>';
|
||||
}
|
||||
|
||||
$item = array();
|
||||
|
@@ -34,13 +34,29 @@ class GithubSearchBridge extends BridgeAbstract {
|
||||
$title = $element->find('h3', 0)->plaintext;
|
||||
$item['title'] = $title;
|
||||
|
||||
if (count($element->find('p')) == 2) {
|
||||
$content = $element->find('p', 0)->innertext;
|
||||
// Description
|
||||
if (count($element->find('p.d-inline-block')) != 0) {
|
||||
$content = $element->find('p.d-inline-block', 0)->innertext;
|
||||
} else{
|
||||
$content = '';
|
||||
$content = 'No description';
|
||||
}
|
||||
$item['content'] = $content;
|
||||
|
||||
// Tags
|
||||
$content = $content . '<br />';
|
||||
$tags = $element->find('a.topic-tag');
|
||||
$tags_array = array();
|
||||
if (count($tags) != 0) {
|
||||
$content = $content . 'Tags : ';
|
||||
foreach($tags as $tag_element) {
|
||||
$tag_link = 'https://github.com' . $tag_element->href;
|
||||
$tag_name = trim($tag_element->innertext);
|
||||
$content = $content . '<a href="' . $tag_link . '">' . $tag_name . '</a> ';
|
||||
array_push($tags_array, $tag_element->innertext);
|
||||
}
|
||||
}
|
||||
|
||||
$item['categories'] = $tags_array;
|
||||
$item['content'] = $content;
|
||||
$date = $element->find('relative-time', 0)->datetime;
|
||||
$item['timestamp'] = strtotime($date);
|
||||
|
||||
|
222
bridges/GlassdoorBridge.php
Executable file
222
bridges/GlassdoorBridge.php
Executable file
@@ -0,0 +1,222 @@
|
||||
<?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_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED | 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;
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ class GoComicsBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'sky';
|
||||
const NAME = 'GoComics Unofficial RSS';
|
||||
const URI = 'http://www.gocomics.com/';
|
||||
const URI = 'https://www.gocomics.com/';
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'The Unofficial GoComics RSS';
|
||||
const PARAMETERS = array( array(
|
||||
@@ -18,25 +18,27 @@ class GoComicsBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request GoComics: ' . $this->getURI());
|
||||
|
||||
foreach($html->find('div.comic__container') as $element) {
|
||||
//Get info from first page
|
||||
$author = preg_replace('/By /', '', $html->find('.media-subheading', 0)->plaintext);
|
||||
|
||||
$img = $element->find('.item-comic-image img', 0);
|
||||
$link = $element->find('a.js-item-comic-link', 0);
|
||||
$comic = $img->src;
|
||||
$title = $link->title;
|
||||
$url = $html->find('input.js-copy-link', 0)->value;
|
||||
$date = substr($title, -10);
|
||||
if (empty($title))
|
||||
$title = 'GoComics ' . $this->getInput('comicname') . ' on ' . $date;
|
||||
$date = strtotime($date);
|
||||
$link = self::URI . $html->find('.gc-deck--cta-0', 0)->find('a', 0)->href;
|
||||
for($i = 0; $i < 5; $i++) {
|
||||
|
||||
$item = array();
|
||||
$item['id'] = $url;
|
||||
$item['uri'] = $url;
|
||||
$item['title'] = $title;
|
||||
$item['author'] = preg_replace('/by /', '', $element->find('a.link-blended small', 0)->plaintext);
|
||||
$item['timestamp'] = $date;
|
||||
$item['content'] = '<img src="' . $comic . '" alt="' . $title . '" />';
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
class GooglePlusPostBridge extends BridgeAbstract{
|
||||
|
||||
protected $_title;
|
||||
protected $_url;
|
||||
private $title;
|
||||
private $url;
|
||||
|
||||
const MAINTAINER = 'Grummfy';
|
||||
const MAINTAINER = 'Grummfy, logmanoriginal';
|
||||
const NAME = 'Google Plus Post Bridge';
|
||||
const URI = 'https://plus.google.com/';
|
||||
const URI = 'https://plus.google.com';
|
||||
const CACHE_TIMEOUT = 600; //10min
|
||||
const DESCRIPTION = 'Returns user public post (without API).';
|
||||
|
||||
@@ -14,10 +14,20 @@ class GooglePlusPostBridge extends BridgeAbstract{
|
||||
'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 getIcon() {
|
||||
return 'https://ssl.gstatic.com/images/branding/product/ico/google_plus_alldp.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$username = $this->getInput('username');
|
||||
|
||||
// Usernames start with a + if it's not an ID
|
||||
@@ -25,22 +35,20 @@ class GooglePlusPostBridge extends BridgeAbstract{
|
||||
$username = '+' . $username;
|
||||
}
|
||||
|
||||
// get content parsed
|
||||
$html = getSimpleHTMLDOMCached(self::URI . urlencode($username) . '/posts')
|
||||
$html = getSimpleHTMLDOM(static::URI . '/' . urlencode($username) . '/posts')
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
// get title, url, ... there is a lot of intresting stuff in meta
|
||||
$this->_title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
|
||||
$this->_url = $html->find('meta[property=og:url]', 0)->getAttribute('content');
|
||||
$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');
|
||||
|
||||
// I don't even know where to start with this discusting html...
|
||||
foreach($html->find('div[jsname=WsjYwc]') as $post) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['author'] = $item['fullname'] = $post->find('div div div div a', 0)->innertext;
|
||||
$item['id'] = $post->find('div div div', 0)->getAttribute('id');
|
||||
$item['avatar'] = $post->find('div img', 0)->src;
|
||||
$item['uri'] = self::URI . $post->find('div div div a', 1)->href;
|
||||
$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);
|
||||
|
||||
@@ -51,61 +59,151 @@ class GooglePlusPostBridge extends BridgeAbstract{
|
||||
$timestamp->getAttribute('aria-label')));
|
||||
}
|
||||
|
||||
// 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;
|
||||
// }
|
||||
$message = $post->find('div[jsname=EjRJtf]', 0);
|
||||
|
||||
$item['content'] = '';
|
||||
// Empty messages are not supported right now
|
||||
if(!$message) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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="'
|
||||
$item['content'] = '<div style="float: left; padding: 0 10px 10px 0;"><a href="'
|
||||
. $this->url
|
||||
. '"><img align="top" alt="'
|
||||
. $item['author']
|
||||
. '" src="'
|
||||
. $item['avatar']
|
||||
. '" /></a></div>';
|
||||
. $post->find('div img', 0)->src
|
||||
. '" /></a></div><div>'
|
||||
. trim(strip_tags($message, '<a><p><div><img>'))
|
||||
. '</div>';
|
||||
|
||||
$content = $post->find('div[jsname=EjRJtf]', 0);
|
||||
// extract plaintext
|
||||
$item['content_simple'] = $content->plaintext;
|
||||
$item['title'] = substr($item['content_simple'], 0, 72) . '...';
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
$content = $content->innertext;
|
||||
|
||||
$item['content'] .= '<div style="margin-top: -1.5em">' . $content . '</div>';
|
||||
$item['content'] = trim(strip_tags($item['content'], '<a><p><div><img>'));
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
return $this->_title ?: 'Google Plus Post Bridge';
|
||||
return $this->title ?: 'Google Plus Post Bridge';
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
return $this->_url ?: parent::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)
|
||||
{
|
||||
|
||||
$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]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ class GoogleSearchBridge extends BridgeAbstract {
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
'q' => array(
|
||||
'name' => "keyword",
|
||||
'name' => 'keyword',
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
@@ -28,7 +28,7 @@ class GoogleSearchBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(self::URI
|
||||
. 'search?q='
|
||||
. urlencode($this->getInput('q'))
|
||||
.'&num=100&complete=0&tbs=qdr:y,sbd:1')
|
||||
. '&num=100&complete=0&tbs=qdr:y,sbd:1')
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
$emIsRes = $html->find('div[id=ires]', 0);
|
||||
|
62
bridges/GrandComicsDatabaseBridge.php
Normal file
62
bridges/GrandComicsDatabaseBridge.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
1397
bridges/HotUKDealsBridge.php
Normal file
1397
bridges/HotUKDealsBridge.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,8 +18,8 @@ class IPBBridge extends FeedExpander {
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Specify how many pages should be fetched (-1: all)',
|
||||
'defaultValue' => 1
|
||||
'title' => 'Specifies the number of items to return on each request (-1: all)',
|
||||
'defaultValue' => 10
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -161,15 +161,18 @@ class IPBBridge extends FeedExpander {
|
||||
|
||||
$next = null; // Holds the URI of the next page
|
||||
|
||||
do {
|
||||
// Skip loading HTML on first iteration
|
||||
if(!is_null($next)) {
|
||||
$html = getSimpleHTMLDOMCached($next);
|
||||
while(true) {
|
||||
$next = $this->$callback($html, is_null($next));
|
||||
|
||||
if(is_null($next) || ($limit > 0 && count($this->items) >= $limit)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$next = $this->$callback($html, is_null($next));
|
||||
$limit--;
|
||||
} while(!is_null($next) && $limit <> 0);
|
||||
$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){
|
||||
|
@@ -3,78 +3,137 @@ class InstagramBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'pauder';
|
||||
const NAME = 'Instagram Bridge';
|
||||
const URI = 'https://instagram.com/';
|
||||
const URI = 'https://www.instagram.com/';
|
||||
const DESCRIPTION = 'Returns the newest images';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'u' => array(
|
||||
'name' => 'username',
|
||||
'required' => true
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'u' => array(
|
||||
'name' => 'username',
|
||||
'required' => true
|
||||
)
|
||||
),
|
||||
'media_type' => array(
|
||||
'name' => 'Media type',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'Both' => 'all',
|
||||
'Video' => 'video',
|
||||
'Picture' => 'picture'
|
||||
),
|
||||
'defaultValue' => 'all'
|
||||
array(
|
||||
'h' => array(
|
||||
'name' => 'hashtag',
|
||||
'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;
|
||||
|
||||
foreach($html->find('script') as $script) {
|
||||
if('' === $script->innertext) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pos = strpos(trim($script->innertext), 'window._sharedData');
|
||||
if(0 !== $pos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$innertext = $script->innertext;
|
||||
break;
|
||||
if(!is_null($this->getInput('h')) && $this->getInput('media_type') == 'story') {
|
||||
returnClientError('Stories are not supported for hashtags!');
|
||||
}
|
||||
|
||||
$json = trim(substr($innertext, $pos + 18), ' =;');
|
||||
$data = json_decode($json);
|
||||
$data = $this->getInstagramJSON($this->getURI());
|
||||
|
||||
$userMedia = $data->entry_data->ProfilePage[0]->user->media->nodes;
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
$userMedia = $data->entry_data->ProfilePage[0]->graphql->user->edge_owner_to_timeline_media->edges;
|
||||
} else {
|
||||
$userMedia = $data->entry_data->TagPage[0]->graphql->hashtag->edge_hashtag_to_media->edges;
|
||||
}
|
||||
|
||||
foreach($userMedia as $media) {
|
||||
// Check media type
|
||||
switch($this->getInput('media_type')) {
|
||||
case 'all': break;
|
||||
case 'video':
|
||||
if($media->is_video === false) continue 2;
|
||||
break;
|
||||
case 'picture':
|
||||
if($media->is_video === true) continue 2;
|
||||
break;
|
||||
default: break;
|
||||
$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->code . '/';
|
||||
$item['content'] = '<img src="' . htmlentities($media->display_src) . '" />';
|
||||
if (isset($media->caption)) {
|
||||
$item['title'] = $media->caption;
|
||||
$item['uri'] = self::URI . 'p/' . $media->shortcode . '/';
|
||||
|
||||
if (isset($media->edge_media_to_caption->edges[0]->node->text)) {
|
||||
$item['title'] = $media->edge_media_to_caption->edges[0]->node->text;
|
||||
} else {
|
||||
$item['title'] = basename($media->display_src);
|
||||
$item['title'] = basename($media->display_url);
|
||||
}
|
||||
$item['timestamp'] = $media->date;
|
||||
|
||||
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'] = '<img src="' . htmlentities($media->display_url) . '" alt="' . $item['title'] . '" />';
|
||||
$item['enclosures'] = array($media->display_url);
|
||||
}
|
||||
|
||||
$item['timestamp'] = $media->taken_at_timestamp;
|
||||
|
||||
$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';
|
||||
@@ -85,7 +144,9 @@ class InstagramBridge extends BridgeAbstract {
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return self::URI . urlencode($this->getInput('u'));
|
||||
return self::URI . urlencode($this->getInput('u')) . '/';
|
||||
} elseif(!is_null($this->getInput('h'))) {
|
||||
return self::URI . 'explore/tags/' . urlencode($this->getInput('h'));
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
|
370
bridges/InstructablesBridge.php
Normal file
370
bridges/InstructablesBridge.php
Normal 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(''', '', $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>';
|
||||
}
|
||||
}
|
@@ -1,465 +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->buildCategoryUri(
|
||||
$this->getInput('torrent_category'),
|
||||
$this->getInput('torrent_popularity')
|
||||
);
|
||||
break;
|
||||
case 'Search torrent by name':
|
||||
$category = $this->getInput('search_category');
|
||||
$uri .= $this->buildCategoryUri($category);
|
||||
if($category !== 'movies')
|
||||
$uri .= '&ihq=' . urlencode($this->getInput('search_name'));
|
||||
break;
|
||||
|
||||
default: parent::getURI();
|
||||
}
|
||||
|
||||
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;
|
||||
default: return parent::getName();
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = $this->loadHtml($this->getURI());
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'By "Latest" category':
|
||||
switch($this->getInput('latest_category')) {
|
||||
case 'hot_torrents':
|
||||
$this->getLatestHotTorrents($html);
|
||||
break;
|
||||
case 'news':
|
||||
$this->getLatestNews($html);
|
||||
break;
|
||||
case 'releases':
|
||||
case 'torrents':
|
||||
$this->getLatestTorrents($html);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'By "Torrent" category':
|
||||
if($this->getInput('torrent_category') === 'movies') {
|
||||
// This one is special (content wise)
|
||||
$this->getMovieTorrents($html);
|
||||
} else {
|
||||
$this->getLatestTorrents($html);
|
||||
}
|
||||
break;
|
||||
case 'Search torrent by name':
|
||||
if($this->getInput('search_category') === 'movies') {
|
||||
// This one is special (content wise)
|
||||
$this->getMovieTorrents($html);
|
||||
} else {
|
||||
$this->getLatestTorrents($html);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#region Helper functions for "Movie Torrents"
|
||||
|
||||
private function getMovieTorrents($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->fixRelativeUri($anchor->href);
|
||||
$item['title'] = $anchor->title;
|
||||
// $item['author'] =
|
||||
$item['timestamp'] = strtotime($date->plaintext);
|
||||
$item['content'] = $this->fixRelativeUri($torrent->innertext);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper functions for "Latest Hot Torrents"
|
||||
|
||||
private function getLatestHotTorrents($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 getLatestNews($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->latestNewsExtractUri($post);
|
||||
$item['title'] = $this->latestNewsExtractTitle($post);
|
||||
$item['author'] = $this->latestNewsExtractAuthor($post);
|
||||
$item['timestamp'] = $this->latestNewsExtractTimestamp($post);
|
||||
$item['content'] = $this->latestNewsExtractContent($post);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function latestNewsExtractAuthor($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 latestNewsExtractTimestamp($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 latestNewsExtractTitle($post){
|
||||
$title = $post->find('a', 0);
|
||||
if(!$title)
|
||||
returnServerError('Unable to find title!');
|
||||
|
||||
return $title->plaintext;
|
||||
}
|
||||
|
||||
private function latestNewsExtractUri($post){
|
||||
$uri = $post->find('a', 0);
|
||||
if(!$uri)
|
||||
returnServerError('Unable to find uri!');
|
||||
|
||||
return $uri->href;
|
||||
}
|
||||
|
||||
private function latestNewsExtractContent($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 getLatestTorrents($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->latestTorrentsExtractUri($torrent);
|
||||
$item['title'] = $this->latestTorrentsExtractTitle($torrent);
|
||||
$item['author'] = $this->latestTorrentsExtractAuthor($torrent);
|
||||
$item['timestamp'] = $this->latestTorrentsExtractTimestamp($torrent);
|
||||
$item['content'] = ''; // There is no valuable content
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function latestTorrentsExtractTitle($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 latestTorrentsExtractUri($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->fixRelativeUri($uri->href);
|
||||
}
|
||||
|
||||
private function latestTorrentsExtractAuthor($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 latestTorrentsExtractTimestamp($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 loadHtml($uri){
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
if(!$html)
|
||||
returnServerError('Unable to load ' . $uri . '!');
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function fixRelativeUri($uri){
|
||||
return preg_replace('/\//i', self::URI, $uri, 1);
|
||||
}
|
||||
|
||||
private function buildCategoryUri($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
|
||||
}
|
@@ -3,7 +3,7 @@ class JapanExpoBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Ginko';
|
||||
const NAME = 'Japan Expo Actualités';
|
||||
const URI = 'http://www.japan-expo-paris.com/fr/actualites';
|
||||
const URI = 'https://www.japan-expo-paris.com/fr/actualites';
|
||||
const CACHE_TIMEOUT = 14400; // 4h
|
||||
const DESCRIPTION = 'Returns most recent entries from Japan Expo actualités.';
|
||||
const PARAMETERS = array( array(
|
||||
@@ -13,6 +13,10 @@ class JapanExpoBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://s.japan-expo.com/katana/images/JES073/favicons/paris.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
function frenchPubDateToTimestamp($date_to_parse) {
|
||||
@@ -51,7 +55,7 @@ class JapanExpoBridge extends BridgeAbstract {
|
||||
foreach($html->find('a._tile2') as $element) {
|
||||
|
||||
$url = $element->href;
|
||||
$thumbnail = 'http://s.japan-expo.com/katana/images/JES049/paris.png';
|
||||
$thumbnail = 'https://s.japan-expo.com/katana/images/JES049/paris.png';
|
||||
preg_match('/url\(([^)]+)\)/', $element->find('img.rspvimgset', 0)->style, $img_search_result);
|
||||
|
||||
if(count($img_search_result) >= 2)
|
||||
@@ -62,7 +66,8 @@ class JapanExpoBridge extends BridgeAbstract {
|
||||
break;
|
||||
}
|
||||
|
||||
$article_html = getSimpleHTMLDOMCached('Could not request JapanExpo: ' . $url);
|
||||
$article_html = getSimpleHTMLDOMCached($url)
|
||||
or returnServerError('Could not request JapanExpo: ' . $url);
|
||||
$header = $article_html->find('header.pageHeadBox', 0);
|
||||
$timestamp = strtotime($header->find('time', 0)->datetime);
|
||||
$title_html = $header->find('div.section', 0)->next_sibling();
|
||||
@@ -92,6 +97,7 @@ class JapanExpoBridge extends BridgeAbstract {
|
||||
$item['uri'] = $url;
|
||||
$item['title'] = $title;
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['enclosures'] = array($thumbnail);
|
||||
$item['content'] = $content;
|
||||
$this->items[] = $item;
|
||||
$count++;
|
||||
|
353
bridges/JustETFBridge.php
Normal file
353
bridges/JustETFBridge.php
Normal file
@@ -0,0 +1,353 @@
|
||||
<?php
|
||||
class JustETFBridge extends BridgeAbstract {
|
||||
const NAME = 'justETF Bridge';
|
||||
const URI = 'https://www.justetf.com';
|
||||
const DESCRIPTION = 'Currently only supports the news feed';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const PARAMETERS = array(
|
||||
'News' => array(
|
||||
'full' => array(
|
||||
'name' => 'Full Article',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Enable to load full articles'
|
||||
)
|
||||
),
|
||||
'Profile' => array(
|
||||
'isin' => array(
|
||||
'name' => 'ISIN',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'pattern' => '[a-zA-Z]{2}[a-zA-Z0-9]{10}',
|
||||
'title' => 'ISIN, consisting of 2-letter country code, 9-character identifier, check character'
|
||||
),
|
||||
'strategy' => array(
|
||||
'name' => 'Include Strategy',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'description' => array(
|
||||
'name' => 'Include Description',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
),
|
||||
'global' => array(
|
||||
'lang' => array(
|
||||
'name' => 'Language',
|
||||
'required' => true,
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Englisch' => 'en',
|
||||
'Deutsch' => 'de',
|
||||
'Italiano' => 'it'
|
||||
),
|
||||
'defaultValue' => 'Englisch'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Failed loading contents from ' . $this->getURI());
|
||||
|
||||
defaultLinkTo($html, static::URI);
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'News':
|
||||
$this->collectNews($html);
|
||||
break;
|
||||
case 'Profile':
|
||||
$this->collectProfile($html);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = static::URI;
|
||||
|
||||
if($this->getInput('lang')) {
|
||||
$uri .= '/' . $this->getInput('lang');
|
||||
}
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'News':
|
||||
$uri .= '/news';
|
||||
break;
|
||||
case 'Profile':
|
||||
$uri .= '/etf-profile.html?' . http_build_query(array(
|
||||
'isin' => strtoupper($this->getInput('isin'))
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
$name = static::NAME;
|
||||
|
||||
$name .= ($this->queriedContext) ? ' - ' . $this->queriedContext : '';
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'News': break;
|
||||
case 'Profile':
|
||||
if($this->getInput('isin')) {
|
||||
$name .= ' ISIN ' . strtoupper($this->getInput('isin'));
|
||||
}
|
||||
}
|
||||
|
||||
if($this->getInput('lang')) {
|
||||
$name .= ' (' . strtoupper($this->getInput('lang')) . ')';
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
#region Common
|
||||
|
||||
/**
|
||||
* Fixes dates depending on the choosen language:
|
||||
*
|
||||
* de : dd.mm.yy
|
||||
* en : dd.mm.yy
|
||||
* it : dd/mm/yy
|
||||
*
|
||||
* Basically strtotime doesn't convert dates correctly due to formats
|
||||
* being hard to interpret. So we use the DateTime object, manually
|
||||
* fixing dates and times (set to 00:00:00.000).
|
||||
*
|
||||
* We don't know the timezone, so just assume +00:00 (or whatever
|
||||
* DateTime chooses)
|
||||
*/
|
||||
private function fixDate($date) {
|
||||
switch($this->getInput('lang')) {
|
||||
case 'en':
|
||||
case 'de':
|
||||
$df = date_create_from_format('d.m.y', $date);
|
||||
break;
|
||||
case 'it':
|
||||
$df = date_create_from_format('d/m/y', $date);
|
||||
break;
|
||||
}
|
||||
|
||||
date_time_set($df, 0, 0);
|
||||
|
||||
// debugMessage(date_format($df, 'U'));
|
||||
|
||||
return date_format($df, 'U');
|
||||
}
|
||||
|
||||
private function extractImages($article) {
|
||||
// Notice: We can have zero or more images (though it should mostly be 1)
|
||||
$elements = $article->find('img');
|
||||
|
||||
$images = array();
|
||||
|
||||
foreach($elements as $img) {
|
||||
// Skip the logo (mostly provided part of a hidden div)
|
||||
if(substr($img->src, strrpos($img->src, '/') + 1) === 'logo.png')
|
||||
continue;
|
||||
|
||||
$images[] = $img->src;
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region News
|
||||
|
||||
private function collectNews($html) {
|
||||
$articles = $html->find('div.newsTopArticle')
|
||||
or returnServerError('No articles found! Layout might have changed!');
|
||||
|
||||
foreach($articles as $article) {
|
||||
|
||||
$item = array();
|
||||
|
||||
// Common data
|
||||
|
||||
$item['uri'] = $this->extractNewsUri($article);
|
||||
$item['timestamp'] = $this->extractNewsDate($article);
|
||||
$item['title'] = $this->extractNewsTitle($article);
|
||||
|
||||
if($this->getInput('full')) {
|
||||
|
||||
$uri = $this->extractNewsUri($article);
|
||||
|
||||
$html = getSimpleHTMLDOMCached($uri)
|
||||
or returnServerError('Failed loading full article from ' . $uri);
|
||||
|
||||
$fullArticle = $html->find('div.article', 0)
|
||||
or returnServerError('No content found! Layout might have changed!');
|
||||
|
||||
defaultLinkTo($fullArticle, static::URI);
|
||||
|
||||
$item['author'] = $this->extractFullArticleAuthor($fullArticle);
|
||||
$item['content'] = $this->extractFullArticleContent($fullArticle);
|
||||
$item['enclosures'] = $this->extractImages($fullArticle);
|
||||
|
||||
} else {
|
||||
|
||||
$item['content'] = $this->extractNewsDescription($article);
|
||||
$item['enclosures'] = $this->extractImages($article);
|
||||
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractNewsUri($article) {
|
||||
$element = $article->find('a', 0)
|
||||
or returnServerError('Anchor not found!');
|
||||
|
||||
return $element->href;
|
||||
}
|
||||
|
||||
private function extractNewsDate($article) {
|
||||
$element = $article->find('div.subheadline', 0)
|
||||
or returnServerError('Date not found!');
|
||||
|
||||
// debugMessage($element->plaintext);
|
||||
|
||||
$date = trim(explode('|', $element->plaintext)[0]);
|
||||
|
||||
return $this->fixDate($date);
|
||||
}
|
||||
|
||||
private function extractNewsDescription($article) {
|
||||
$element = $article->find('span.newsText', 0)
|
||||
or returnServerError('Description not found!');
|
||||
|
||||
$element->find('a', 0)->onclick = '';
|
||||
|
||||
// debugMessage($element->innertext);
|
||||
|
||||
return $element->innertext;
|
||||
}
|
||||
|
||||
private function extractNewsTitle($article) {
|
||||
$element = $article->find('h3', 0)
|
||||
or returnServerError('Title not found!');
|
||||
|
||||
return $element->plaintext;
|
||||
}
|
||||
|
||||
private function extractFullArticleContent($article) {
|
||||
$element = $article->find('div.article_body', 0)
|
||||
or returnServerError('Article body not found!');
|
||||
|
||||
// Remove teaser image
|
||||
$element->find('img.teaser-img', 0)->outertext = '';
|
||||
|
||||
// Remove self advertisements
|
||||
foreach($element->find('.call-action') as $adv) {
|
||||
$adv->outertext = '';
|
||||
}
|
||||
|
||||
// Remove tips
|
||||
foreach($element->find('.panel-edu') as $tip) {
|
||||
$tip->outertext = '';
|
||||
}
|
||||
|
||||
// Remove inline scripts (used for i.e. interactive graphs) as they are
|
||||
// rendered as a long series of strings
|
||||
foreach($element->find('script') as $script) {
|
||||
$script->outertext = '[Content removed! Visit site to see full contents!]';
|
||||
}
|
||||
|
||||
return $element->innertext;
|
||||
}
|
||||
|
||||
private function extractFullArticleAuthor($article) {
|
||||
$element = $article->find('span[itemprop=name]', 0)
|
||||
or returnServerError('Author not found!');
|
||||
|
||||
return $element->plaintext;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Profile
|
||||
|
||||
private function collectProfile($html) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $this->getURI();
|
||||
$item['timestamp'] = $this->extractProfileDate($html);
|
||||
$item['title'] = $this->extractProfiletitle($html);
|
||||
$item['author'] = $this->extractProfileAuthor($html);
|
||||
$item['content'] = $this->extractProfileContent($html);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
private function extractProfileDate($html) {
|
||||
$element = $html->find('div.infobox div.vallabel', 0)
|
||||
or returnServerError('Date not found!');
|
||||
|
||||
// debugMessage($element->plaintext);
|
||||
|
||||
$date = trim(explode("\r\n", $element->plaintext)[1]);
|
||||
|
||||
return $this->fixDate($date);
|
||||
}
|
||||
|
||||
private function extractProfileTitle($html) {
|
||||
$element = $html->find('span.h1', 0)
|
||||
or returnServerError('Title not found!');
|
||||
|
||||
return $element->plaintext;
|
||||
}
|
||||
|
||||
private function extractProfileContent($html) {
|
||||
// There are a few thins we are interested:
|
||||
// - Investment Strategy
|
||||
// - Description
|
||||
// - Quote
|
||||
|
||||
$strategy = $html->find('div.tab-container div.col-sm-6 p', 0)
|
||||
or returnServerError('Investment Strategy not found!');
|
||||
|
||||
// Description requires a bit of cleanup due to lack of propper identification
|
||||
|
||||
$description = $html->find('div.headline', 5)
|
||||
or returnServerError('Description container not found!');
|
||||
|
||||
$description = $description->parent();
|
||||
|
||||
foreach($description->find('div') as $div) {
|
||||
$div->outertext = '';
|
||||
}
|
||||
|
||||
$quote = $html->find('div.infobox div.val', 0)
|
||||
or returnServerError('Quote not found!');
|
||||
|
||||
$quote_html = '<strong>Quote</strong><br><p>' . $quote . '</p>';
|
||||
$strategy_html = '';
|
||||
$description_html = '';
|
||||
|
||||
if($this->getInput('strategy') === true) {
|
||||
$strategy_html = '<strong>Strategy</strong><br><p>' . $strategy . '</p><br>';
|
||||
}
|
||||
|
||||
if($this->getInput('description') === true) {
|
||||
$description_html = '<strong>Description</strong><br><p>' . $description . '</p><br>';
|
||||
}
|
||||
|
||||
return $strategy_html . $description_html . $quote_html;
|
||||
}
|
||||
|
||||
private function extractProfileAuthor($html) {
|
||||
// Use ISIN + WKN as author
|
||||
// Notice: "identfier" is not a typo [sic]!
|
||||
$element = $html->find('span.identfier', 0)
|
||||
or returnServerError('Author not found!');
|
||||
|
||||
return $element->plaintext;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
@@ -36,6 +36,11 @@ class KATBridge extends BridgeAbstract {
|
||||
'name' => 'Only get results from Elite or Verified uploaders ?',
|
||||
),
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://statuskatcrco-631f.kxcdn.com/assets/images/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
function parseDateTimestamp($element){
|
||||
$guessedDate = strptime($element, '%d-%m-%Y %H:%M:%S');
|
||||
|
@@ -38,6 +38,10 @@ class KernelBugTrackerBridge extends BridgeAbstract {
|
||||
private $bugid = '';
|
||||
private $bugdesc = '';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . '/images/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$limit = $this->getInput('limit');
|
||||
$sorting = $this->getInput('sorting');
|
||||
@@ -45,9 +49,7 @@ class KernelBugTrackerBridge extends BridgeAbstract {
|
||||
// We use the print preview page for simplicity
|
||||
$html = getSimpleHTMLDOMCached($this->getURI() . '&format=multiple',
|
||||
86400,
|
||||
false,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
|
@@ -58,13 +58,13 @@ class KununuBridge extends BridgeAbstract {
|
||||
break;
|
||||
}
|
||||
|
||||
return self::URI . $site . '/' . $company . '/' . $section;
|
||||
return self::URI . $site . '/' . $company . '/' . $section . '?sort=update_time_desc';
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
function getName(){
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('company'))) {
|
||||
$company = $this->fixCompanyName($this->getInput('company'));
|
||||
return ($this->companyName ?: $company) . ' - ' . self::NAME;
|
||||
@@ -73,52 +73,67 @@ class KununuBridge extends BridgeAbstract {
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.kununu.com/favicon-196x196.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$full = $this->getInput('full');
|
||||
|
||||
// Load page
|
||||
$html = getSimpleHTMLDOMCached($this->getURI());
|
||||
if(!$html)
|
||||
returnServerError('Unable to receive data from ' . $this->getURI() . '!');
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Unable to receive data from ' . $this->getURI() . '!');
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
// Update name for this request
|
||||
$this->companyName = $this->extractCompanyName($html);
|
||||
$company = $html->find('span[class="company-name"]', 0)
|
||||
or returnServerError('Cannot find company name!');
|
||||
|
||||
$this->companyName = $company->innertext;
|
||||
|
||||
// Find the section with all the panels (reviews)
|
||||
$section = $html->find('section.kununu-scroll-element', 0);
|
||||
if($section === false)
|
||||
returnServerError('Unable to find panel section!');
|
||||
$section = $html->find('section.kununu-scroll-element', 0)
|
||||
or returnServerError('Unable to find panel section!');
|
||||
|
||||
// Find all articles (within the panels)
|
||||
$articles = $section->find('article');
|
||||
if($articles === false || empty($articles))
|
||||
returnServerError('Unable to find articles!');
|
||||
$articles = $section->find('article')
|
||||
or returnServerError('Unable to find articles!');
|
||||
|
||||
// Go through all articles
|
||||
foreach($articles as $article) {
|
||||
|
||||
$anchor = $article->find('h1.review-title a', 0)
|
||||
or returnServerError('Cannot find article URI!');
|
||||
|
||||
$date = $article->find('meta[itemprop=dateCreated]', 0)
|
||||
or returnServerError('Cannot find article date!');
|
||||
|
||||
$rating = $article->find('span.rating', 0)
|
||||
or returnServerError('Cannot find article rating!');
|
||||
|
||||
$summary = $article->find('[itemprop=name]', 0)
|
||||
or returnServerError('Cannot find article summary!');
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['author'] = $this->extractArticleAuthorPosition($article);
|
||||
$item['timestamp'] = $this->extractArticleDate($article);
|
||||
$item['title'] = $this->extractArticleRating($article)
|
||||
$item['timestamp'] = strtotime($date);
|
||||
$item['title'] = $rating->getAttribute('aria-label')
|
||||
. ' : '
|
||||
. $this->extractArticleSummary($article);
|
||||
. strip_tags($summary->innertext);
|
||||
|
||||
$item['uri'] = $this->extractArticleUri($article);
|
||||
$item['uri'] = $anchor->href;
|
||||
|
||||
if($full)
|
||||
if($full) {
|
||||
$item['content'] = $this->extractFullDescription($item['uri']);
|
||||
else
|
||||
} else {
|
||||
$item['content'] = $this->extractArticleDescription($article);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes relative URLs in the given text
|
||||
*/
|
||||
private function fixUrl($text){
|
||||
return preg_replace('/href=(\'|\")\//i', 'href="'.self::URI, $text);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -128,73 +143,11 @@ class KununuBridge extends BridgeAbstract {
|
||||
$company = trim($company);
|
||||
$company = str_replace(' ', '-', $company);
|
||||
$company = strtolower($company);
|
||||
return $this->encodeUmlauts($company);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes unmlauts in the given text
|
||||
*/
|
||||
private function encodeUmlauts($text){
|
||||
$umlauts = Array("/ä/","/ö/","/ü/","/Ä/","/Ö/","/Ü/","/ß/");
|
||||
$replace = Array("ae","oe","ue","Ae","Oe","Ue","ss");
|
||||
$umlauts = Array('/ä/','/ö/','/ü/','/Ä/','/Ö/','/Ü/','/ß/');
|
||||
$replace = Array('ae','oe','ue','Ae','Oe','Ue','ss');
|
||||
|
||||
return preg_replace($umlauts, $replace, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the company name from the review html
|
||||
*/
|
||||
private function extractCompanyName($html){
|
||||
$company_name = $html->find('h1[itemprop=name]', 0);
|
||||
if(is_null($company_name))
|
||||
returnServerError('Cannot find company name!');
|
||||
|
||||
return $company_name->plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date from a given article
|
||||
*/
|
||||
private function extractArticleDate($article){
|
||||
// They conviniently provide a time attribute for us :)
|
||||
$date = $article->find('meta[itemprop=dateCreated]', 0);
|
||||
if(is_null($date))
|
||||
returnServerError('Cannot find article date!');
|
||||
|
||||
return strtotime($date->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rating from a given article
|
||||
*/
|
||||
private function extractArticleRating($article){
|
||||
$rating = $article->find('span.rating', 0);
|
||||
if(is_null($rating))
|
||||
returnServerError('Cannot find article rating!');
|
||||
|
||||
return $rating->getAttribute('aria-label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the summary from a given article
|
||||
*/
|
||||
private function extractArticleSummary($article){
|
||||
$summary = $article->find('[itemprop=name]', 0);
|
||||
if(is_null($summary))
|
||||
returnServerError('Cannot find article summary!');
|
||||
|
||||
return strip_tags($summary->innertext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI from a given article
|
||||
*/
|
||||
private function extractArticleUri($article){
|
||||
$anchor = $article->find('ku-company-review-more', 0);
|
||||
if(is_null($anchor))
|
||||
returnServerError('Cannot find article URI!');
|
||||
|
||||
return self::URI . $anchor->{'review-url'};
|
||||
return preg_replace($umlauts, $replace, $company);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,9 +155,8 @@ class KununuBridge extends BridgeAbstract {
|
||||
*/
|
||||
private function extractArticleAuthorPosition($article){
|
||||
// We need to parse the user-content manually
|
||||
$user_content = $article->find('div.user-content', 0);
|
||||
if(is_null($user_content))
|
||||
returnServerError('Cannot find user content!');
|
||||
$user_content = $article->find('div.user-content', 0)
|
||||
or returnServerError('Cannot find user content!');
|
||||
|
||||
// Go through all h2 elements to find index of required span (I know... it's stupid)
|
||||
$author_position = 'Unknown';
|
||||
@@ -222,11 +174,10 @@ class KununuBridge extends BridgeAbstract {
|
||||
* Returns the description from a given article
|
||||
*/
|
||||
private function extractArticleDescription($article){
|
||||
$description = $article->find('[itemprop=reviewBody]', 0);
|
||||
if(is_null($description))
|
||||
returnServerError('Cannot find article description!');
|
||||
$description = $article->find('[itemprop=reviewBody]', 0)
|
||||
or returnServerError('Cannot find article description!');
|
||||
|
||||
return $this->fixUrl($description->innertext);
|
||||
return $description->innertext;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,14 +185,14 @@ class KununuBridge extends BridgeAbstract {
|
||||
*/
|
||||
private function extractFullDescription($uri){
|
||||
// Load full article
|
||||
$html = getSimpleHTMLDOMCached($uri);
|
||||
if($html === false)
|
||||
returnServerError('Could not load full description!');
|
||||
$html = getSimpleHTMLDOMCached($uri)
|
||||
or returnServerError('Could not load full description!');
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
// Find the article
|
||||
$article = $html->find('article', 0);
|
||||
if(is_null($article))
|
||||
returnServerError('Cannot find article!');
|
||||
$article = $html->find('article', 0)
|
||||
or returnServerError('Cannot find article!');
|
||||
|
||||
// Luckily they use the same layout for the review overview and full article pages :)
|
||||
return $this->extractArticleDescription($article);
|
||||
|
@@ -9,7 +9,7 @@ class LWNprevBridge extends BridgeAbstract{
|
||||
private $editionTimeStamp;
|
||||
|
||||
function getURI(){
|
||||
return self::URI.'free/bigpage';
|
||||
return self::URI . 'free/bigpage';
|
||||
}
|
||||
|
||||
private function jumpToNextTag(&$node){
|
||||
@@ -47,7 +47,7 @@ class LWNprevBridge extends BridgeAbstract{
|
||||
<html><head><title>LWN</title></head><body>{$content}</body></html>
|
||||
EOD;
|
||||
} else {
|
||||
$content = $content.'</body></html>';
|
||||
$content = $content . '</body></html>';
|
||||
}
|
||||
|
||||
libxml_use_internal_errors(true);
|
||||
@@ -172,7 +172,7 @@ EOD;
|
||||
|
||||
$prefix = '';
|
||||
if(!empty($cats[0])) {
|
||||
$prefix .= '['.$cats[0].($cats[1] ? '/'.$cats[1] : '').'] ';
|
||||
$prefix .= '[' . $cats[0] . ($cats[1] ? '/' . $cats[1] : '') . '] ';
|
||||
}
|
||||
return $prefix;
|
||||
}
|
||||
@@ -188,7 +188,7 @@ EOD;
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = self::URI.'#'.count($items);
|
||||
$item['uri'] = self::URI . '#' . count($items);
|
||||
|
||||
$item['timestamp'] = $this->editionTimeStamp;
|
||||
|
||||
@@ -197,7 +197,7 @@ EOD;
|
||||
$cat = $newsletters->previousSibling;
|
||||
$this->jumpToPreviousTag($cat);
|
||||
$prefix = $this->getItemPrefix($cat, $cats);
|
||||
$item['title'] = $prefix.' '.$newsletters->textContent;
|
||||
$item['title'] = $prefix . ' ' . $newsletters->textContent;
|
||||
|
||||
$node = $newsletters;
|
||||
$content = '';
|
||||
@@ -233,7 +233,7 @@ EOD;
|
||||
$cat = $cat->previousSibling;
|
||||
$this->jumpToPreviousTag($cat);
|
||||
$prefix = $this->getItemPrefix($cat, $cats);
|
||||
$item['title'] = $prefix.' '.$title->textContent;
|
||||
$item['title'] = $prefix . ' ' . $title->textContent;
|
||||
$items[] = array_merge($item, $this->getArticleContent($title));
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ EOD;
|
||||
$cat = $cat->previousSibling;
|
||||
$this->jumpToPreviousTag($cat);
|
||||
$prefix = $this->getItemPrefix($cat, $cats);
|
||||
$item['title'] = $prefix.' '.$title->textContent;
|
||||
$item['title'] = $prefix . ' ' . $title->textContent;
|
||||
$items[] = array_merge($item, $this->getArticleContent($title));
|
||||
}
|
||||
|
||||
|
@@ -1,190 +1,538 @@
|
||||
<?php
|
||||
class LeBonCoinBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = '16mhz';
|
||||
const MAINTAINER = 'jacknumber';
|
||||
const NAME = 'LeBonCoin';
|
||||
const URI = 'https://www.leboncoin.fr/';
|
||||
const DESCRIPTION = 'Returns most recent results from LeBonCoin for a
|
||||
region, and optionally a category and a keyword .';
|
||||
const DESCRIPTION = 'Returns most recent results from LeBonCoin';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'k' => array('name' => 'Mot Clé'),
|
||||
'r' => array(
|
||||
'keywords' => array('name' => 'Mots-Clés'),
|
||||
'region' => array(
|
||||
'name' => 'Région',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Toute la France' => 'ile_de_france/occasions',
|
||||
'Alsace' => 'alsace',
|
||||
'Aquitaine' => 'aquitaine',
|
||||
'Auvergne' => 'auvergne',
|
||||
'Basse Normandie' => 'basse_normandie',
|
||||
'Bourgogne' => 'bourgogne',
|
||||
'Bretagne' => 'bretagne',
|
||||
'Centre' => 'centre',
|
||||
'Champagne Ardenne' => 'champagne_ardenne',
|
||||
'Corse' => 'corse',
|
||||
'Franche Comté' => 'franche_comte',
|
||||
'Haute Normandie' => 'haute_normandie',
|
||||
'Ile de France' => 'ile_de_france',
|
||||
'Languedoc Roussillon' => 'languedoc_roussillon',
|
||||
'Limousin' => 'limousin',
|
||||
'Lorraine' => 'lorraine',
|
||||
'Midi Pyrénées' => 'midi_pyrenees',
|
||||
'Nord Pas De Calais' => 'nord_pas_de_calais',
|
||||
'Pays de la Loire' => 'pays_de_la_loire',
|
||||
'Picardie' => 'picardie',
|
||||
'Poitou Charentes' => 'poitou_charentes',
|
||||
'Provence Alpes Côte d\'Azur' => 'provence_alpes_cote_d_azur',
|
||||
'Rhône-Alpes' => 'rhone_alpes',
|
||||
'Guadeloupe' => 'guadeloupe',
|
||||
'Martinique' => 'martinique',
|
||||
'Guyane' => 'guyane',
|
||||
'Réunion' => 'reunion'
|
||||
'Toute la France' => '',
|
||||
'Alsace' => '1',
|
||||
'Aquitaine' => '2',
|
||||
'Auvergne' => '3',
|
||||
'Basse Normandie' => '4',
|
||||
'Bourgogne' => '5',
|
||||
'Bretagne' => '6',
|
||||
'Centre' => '7',
|
||||
'Champagne Ardenne' => '8',
|
||||
'Corse' => '9',
|
||||
'Franche Comté' => '10',
|
||||
'Haute Normandie' => '11',
|
||||
'Ile de France' => '12',
|
||||
'Languedoc Roussillon' => '13',
|
||||
'Limousin' => '14',
|
||||
'Lorraine' => '15',
|
||||
'Midi Pyrénées' => '16',
|
||||
'Nord Pas De Calais' => '17',
|
||||
'Pays de la Loire' => '18',
|
||||
'Picardie' => '19',
|
||||
'Poitou Charentes' => '20',
|
||||
'Provence Alpes Côte d\'Azur' => '21',
|
||||
'Rhône-Alpes' => '22',
|
||||
'Guadeloupe' => '23',
|
||||
'Martinique' => '24',
|
||||
'Guyane' => '25',
|
||||
'Réunion' => '26'
|
||||
)
|
||||
),
|
||||
'c' => array(
|
||||
'department' => array(
|
||||
'name' => 'Département',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'Ain' => '1',
|
||||
'Aisne' => '2',
|
||||
'Allier' => '3',
|
||||
'Alpes-de-Haute-Provence' => '4',
|
||||
'Hautes-Alpes' => '5',
|
||||
'Alpes-Maritimes' => '6',
|
||||
'Ardèche' => '7',
|
||||
'Ardennes' => '8',
|
||||
'Ariège' => '9',
|
||||
'Aube' => '10',
|
||||
'Aude' => '11',
|
||||
'Aveyron' => '12',
|
||||
'Bouches-du-Rhône' => '13',
|
||||
'Calvados' => '14',
|
||||
'Cantal' => '15',
|
||||
'Charente' => '16',
|
||||
'Charente-Maritime' => '17',
|
||||
'Cher' => '18',
|
||||
'Corrèze' => '19',
|
||||
'Corse-du-Sud' => '2A',
|
||||
'Haute-Corse' => '2B',
|
||||
'Côte-d\'Or' => '21',
|
||||
'Côtes-d\'Armor' => '22',
|
||||
'Creuse' => '23',
|
||||
'Dordogne' => '24',
|
||||
'Doubs' => '25',
|
||||
'Drôme' => '26',
|
||||
'Eure' => '27',
|
||||
'Eure-et-Loir' => '28',
|
||||
'Finistère' => '29',
|
||||
'Gard' => '30',
|
||||
'Haute-Garonne' => '31',
|
||||
'Gers' => '32',
|
||||
'Gironde' => '33',
|
||||
'Hérault' => '34',
|
||||
'Ille-et-Vilaine' => '35',
|
||||
'Indre' => '36',
|
||||
'Indre-et-Loire' => '37',
|
||||
'Isère' => '38',
|
||||
'Jura' => '39',
|
||||
'Landes' => '40',
|
||||
'Loir-et-Cher' => '41',
|
||||
'Loire' => '42',
|
||||
'Haute-Loire' => '43',
|
||||
'Loire-Atlantique' => '44',
|
||||
'Loiret' => '45',
|
||||
'Lot' => '46',
|
||||
'Lot-et-Garonne' => '47',
|
||||
'Lozère' => '48',
|
||||
'Maine-et-Loire' => '49',
|
||||
'Manche' => '50',
|
||||
'Marne' => '51',
|
||||
'Haute-Marne' => '52',
|
||||
'Mayenne' => '53',
|
||||
'Meurthe-et-Moselle' => '54',
|
||||
'Meuse' => '55',
|
||||
'Morbihan' => '56',
|
||||
'Moselle' => '57',
|
||||
'Nièvre' => '58',
|
||||
'Nord' => '59',
|
||||
'Oise' => '60',
|
||||
'Orne' => '61',
|
||||
'Pas-de-Calais' => '62',
|
||||
'Puy-de-Dôme' => '63',
|
||||
'Pyrénées-Atlantiques' => '64',
|
||||
'Hautes-Pyrénées' => '65',
|
||||
'Pyrénées-Orientales' => '66',
|
||||
'Bas-Rhin' => '67',
|
||||
'Haut-Rhin' => '68',
|
||||
'Rhône' => '69',
|
||||
'Haute-Saône' => '70',
|
||||
'Saône-et-Loire' => '71',
|
||||
'Sarthe' => '72',
|
||||
'Savoie' => '73',
|
||||
'Haute-Savoie' => '74',
|
||||
'Paris' => '75',
|
||||
'Seine-Maritime' => '76',
|
||||
'Seine-et-Marne' => '77',
|
||||
'Yvelines' => '78',
|
||||
'Deux-Sèvres' => '79',
|
||||
'Somme' => '80',
|
||||
'Tarn' => '81',
|
||||
'Tarn-et-Garonne' => '82',
|
||||
'Var' => '83',
|
||||
'Vaucluse' => '84',
|
||||
'Vendée' => '85',
|
||||
'Vienne' => '86',
|
||||
'Haute-Vienne' => '87',
|
||||
'Vosges' => '88',
|
||||
'Yonne' => '89',
|
||||
'Territoire de Belfort' => '90',
|
||||
'Essonne' => '91',
|
||||
'Hauts-de-Seine' => '92',
|
||||
'Seine-Saint-Denis' => '93',
|
||||
'Val-de-Marne' => '94',
|
||||
'Val-d\'Oise' => '95'
|
||||
)
|
||||
),
|
||||
'cities' => array(
|
||||
'name' => 'Villes',
|
||||
'title' => 'Codes postaux séparés par des virgules'
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Catégorie',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'TOUS' => '',
|
||||
'EMPLOI' => '_emploi_',
|
||||
'VEHICULES' => array(
|
||||
'Tous' => '_vehicules_',
|
||||
'Voitures' => 'voitures',
|
||||
'Motos' => 'motos',
|
||||
'Caravaning' => 'caravaning',
|
||||
'Utilitaires' => 'utilitaires',
|
||||
'Équipement Auto' => 'equipement_auto',
|
||||
'Équipement Moto' => 'equipement_moto',
|
||||
'Équipement Caravaning' => 'equipement_caravaning',
|
||||
'Nautisme' => 'nautisme',
|
||||
'Équipement Nautisme' => 'equipement_nautisme'
|
||||
'Toutes catégories' => '',
|
||||
'EMPLOI' => array(
|
||||
'Emploi et recrutement' => '71',
|
||||
'Offres d\'emploi et jobs' => '33'
|
||||
),
|
||||
'VÉHICULES' => array(
|
||||
'Tous' => '1',
|
||||
'Voitures' => '2',
|
||||
'Motos' => '3',
|
||||
'Caravaning' => '4',
|
||||
'Utilitaires' => '5',
|
||||
'Equipement Auto' => '6',
|
||||
'Equipement Moto' => '44',
|
||||
'Equipement Caravaning' => '50',
|
||||
'Nautisme' => '7',
|
||||
'Equipement Nautisme' => '51'
|
||||
),
|
||||
'IMMOBILIER' => array(
|
||||
'Tous' => '_immobilier_',
|
||||
'Ventes immobilières' => 'ventes_immobilieres',
|
||||
'Locations' => 'locations',
|
||||
'Colocations' => 'colocations',
|
||||
'Bureaux & Commerces' => 'bureaux_commerces'
|
||||
'Tous' => '8',
|
||||
'Ventes immobilières' => '9',
|
||||
'Locations' => '10',
|
||||
'Colocations' => '11',
|
||||
'Bureaux & Commerces' => '13'
|
||||
),
|
||||
'VACANCES' => array(
|
||||
'Tous' => '_vacances_',
|
||||
'Location gîtes' => 'locations_gites',
|
||||
'Chambres d\'hôtes' => 'chambres_d_hotes',
|
||||
'Campings' => 'campings',
|
||||
'Hôtels' => 'hotels',
|
||||
'Hébergements insolites' => 'hebergements_insolites'
|
||||
'Tous' => '66',
|
||||
'Locations & Gîtes' => '12',
|
||||
'Chambres d\'hôtes' => '67',
|
||||
'Campings' => '68',
|
||||
'Hôtels' => '69',
|
||||
'Hébergements insolites' => '70'
|
||||
),
|
||||
'MULTIMEDIA' => array(
|
||||
'Tous' => '_multimedia_',
|
||||
'Informatique' => 'informatique',
|
||||
'Consoles & Jeux vidéo' => 'consoles_jeux_video',
|
||||
'Image & Son' => 'image_son',
|
||||
'Téléphonie' => 'telephonie'
|
||||
'MULTIMÉDIA' => array(
|
||||
'Tous' => '14',
|
||||
'Informatique' => '15',
|
||||
'Consoles & Jeux vidéo' => '43',
|
||||
'Image & Son' => '16',
|
||||
'Téléphonie' => '17'
|
||||
),
|
||||
'LOISIRS' => array(
|
||||
'Tous' => '_loisirs_',
|
||||
'DVD / Films' => 'dvd_films',
|
||||
'CD / Musique' => 'cd_musique',
|
||||
'Livres' => 'livres',
|
||||
'Animaux' => 'animaux',
|
||||
'Vélos' => 'velos',
|
||||
'Sports & Hobbies' => 'sports_hobbies',
|
||||
'Instruments de musique' => 'instruments_de_musique',
|
||||
'Collection' => 'collection',
|
||||
'Jeux & Jouets' => 'jeux_jouets',
|
||||
'Vins & Gastronomie' => 'vins_gastronomie'
|
||||
'Tous' => '24',
|
||||
'DVD / Films' => '25',
|
||||
'CD / Musique' => '26',
|
||||
'Livres' => '27',
|
||||
'Animaux' => '28',
|
||||
'Vélos' => '55',
|
||||
'Sports & Hobbies' => '29',
|
||||
'Instruments de musique' => '30',
|
||||
'Collection' => '40',
|
||||
'Jeux & Jouets' => '41',
|
||||
'Vins & Gastronomie' => '48'
|
||||
),
|
||||
'MATÉRIEL PROFESSIONNEL' => array(
|
||||
'Tous' => '_materiel_professionnel_',
|
||||
'Matériel Agricole' => 'mateiel_agricole',
|
||||
'Transport - Manutention' => 'transport_manutention',
|
||||
'BTP - Chantier - Gros-œuvre' => 'btp_chantier_gros_oeuvre',
|
||||
'Outillage - Matériaux 2nd-œuvre' => 'outillage_materiaux_2nd_oeuvre',
|
||||
'Équipements Industriels' => 'equipement_industriels',
|
||||
'Restauration - Hôtellerie' => 'restauration_hotellerie',
|
||||
'Fournitures de Bureau' => 'fournitures_de_bureau',
|
||||
'Commerces & Marchés' => 'commerces_marches',
|
||||
'Matériel médical' => 'materiel_medical'
|
||||
'Tous' => '56',
|
||||
'Matériel Agricole' => '57',
|
||||
'Transport - Manutention' => '58',
|
||||
'BTP - Chantier Gros-oeuvre' => '59',
|
||||
'Outillage - Matériaux 2nd-oeuvre' => '60',
|
||||
'Équipements Industriels' => '32',
|
||||
'Restauration - Hôtellerie' => '61',
|
||||
'Fournitures de Bureau' => '62',
|
||||
'Commerces & Marchés' => '63',
|
||||
'Matériel Médical' => '64'
|
||||
),
|
||||
'SERVICES' => array(
|
||||
'Tous' => '_services_',
|
||||
'Prestations de services' => 'prestations_de_services',
|
||||
'Billetterie' => 'billetterie',
|
||||
'Évènements' => 'evenements',
|
||||
'Cours particuliers' => 'cours_particuliers',
|
||||
'Covoiturage' => 'covoiturage'
|
||||
'Tous' => '31',
|
||||
'Prestations de services' => '34',
|
||||
'Billetterie' => '35',
|
||||
'Événements' => '49',
|
||||
'Cours particuliers' => '36',
|
||||
'Covoiturage' => '65'
|
||||
),
|
||||
'MAISON' => array(
|
||||
'Tous' => '_maison_',
|
||||
'Ameublement' => 'ameublement',
|
||||
'Électroménager' => 'electromenager',
|
||||
'Arts de la table' => 'arts_de_la_table',
|
||||
'Décoration' => 'decoration',
|
||||
'Linge de maison' => 'linge_de_maison',
|
||||
'Bricolage' => 'bricolage',
|
||||
'Jardinage' => 'jardinage',
|
||||
'Vêtements' => 'vetements',
|
||||
'Chaussures' => 'chaussures',
|
||||
'Accessoires & Bagagerie' => 'accessoires_bagagerie',
|
||||
'Montres & Bijoux' => 'montres_bijoux',
|
||||
'Équipement bébé' => 'equipement_bebe',
|
||||
'Vêtements bébé' => 'vetements_bebe'
|
||||
'Tous' => '18',
|
||||
'Ameublement' => '19',
|
||||
'Électroménager' => '20',
|
||||
'Arts de la table' => '45',
|
||||
'Décoration' => '39',
|
||||
'Linge de maison' => '46',
|
||||
'Bricolage' => '21',
|
||||
'Jardinage' => '52',
|
||||
'Vêtements' => '22',
|
||||
'Chaussures' => '53',
|
||||
'Accessoires & Bagagerie' => '47',
|
||||
'Montres & Bijoux' => '42',
|
||||
'Équipement bébé' => '23',
|
||||
'Vêtements bébé' => '54',
|
||||
),
|
||||
'AUTRES' => 'autres'
|
||||
'AUTRES' => '37'
|
||||
)
|
||||
),
|
||||
'pricemin' => array(
|
||||
'name' => 'Prix min',
|
||||
'type' => 'number'
|
||||
),
|
||||
'pricemax' => array(
|
||||
'name' => 'Prix max',
|
||||
'type' => 'number'
|
||||
),
|
||||
'estate' => array(
|
||||
'name' => 'Type de bien',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'Maison' => '1',
|
||||
'Appartement' => '2',
|
||||
'Terrain' => '3',
|
||||
'Parking' => '4',
|
||||
'Autre' => '5'
|
||||
)
|
||||
),
|
||||
'roomsmin' => array(
|
||||
'name' => 'Pièces min',
|
||||
'type' => 'number'
|
||||
),
|
||||
'roomsmax' => array(
|
||||
'name' => 'Pièces max',
|
||||
'type' => 'number'
|
||||
),
|
||||
'squaremin' => array(
|
||||
'name' => 'Surface min',
|
||||
'type' => 'number'
|
||||
),
|
||||
'squaremax' => array(
|
||||
'name' => 'Surface max',
|
||||
'type' => 'number'
|
||||
),
|
||||
'mileagemin' => array(
|
||||
'name' => 'Kilométrage min',
|
||||
'type' => 'number'
|
||||
),
|
||||
'mileagemax' => array(
|
||||
'name' => 'Kilométrage max',
|
||||
'type' => 'number'
|
||||
),
|
||||
'yearmin' => array(
|
||||
'name' => 'Année min',
|
||||
'type' => 'number'
|
||||
),
|
||||
'yearmax' => array(
|
||||
'name' => 'Année max',
|
||||
'type' => 'number'
|
||||
),
|
||||
'cubiccapacitymin' => array(
|
||||
'name' => 'Cylindrée min',
|
||||
'type' => 'number'
|
||||
),
|
||||
'cubiccapacitymax' => array(
|
||||
'name' => 'Cylindrée max',
|
||||
'type' => 'number'
|
||||
),
|
||||
'fuel' => array(
|
||||
'name' => 'Énergie',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'Essence' => '1',
|
||||
'Diesel' => '2',
|
||||
'GPL' => '3',
|
||||
'Électrique' => '4',
|
||||
'Hybride' => '6',
|
||||
'Autre' => '5'
|
||||
)
|
||||
),
|
||||
'owner' => array(
|
||||
'name' => 'Vendeur',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Tous' => '',
|
||||
'Particuliers' => 'private',
|
||||
'Professionnels' => 'pro'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
public static $LBC_API_KEY = 'ba0c2dad52b3ec';
|
||||
|
||||
$category = $this->getInput('c');
|
||||
if(empty($category)) {
|
||||
$category = 'annonces';
|
||||
private function getRange($field, $range_min, $range_max){
|
||||
|
||||
if(!is_null($range_min)
|
||||
&& !is_null($range_max)
|
||||
&& $range_min > $range_max) {
|
||||
returnClientError('Min-' . $field . ' must be lower than max-' . $field . '.');
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI
|
||||
. $category
|
||||
. '/offres/'
|
||||
. $this->getInput('r')
|
||||
. '/?f=a&th=1&q='
|
||||
. urlencode($this->getInput('k')))
|
||||
or returnServerError('Could not request LeBonCoin.');
|
||||
if(!is_null($range_min)
|
||||
&& is_null($range_max)) {
|
||||
returnClientError('Max-' . $field . ' is needed when min-' . $field . ' is setted (range).');
|
||||
}
|
||||
|
||||
$list = $html->find('.tabsContent', 0);
|
||||
if($list === null) {
|
||||
return array(
|
||||
'min' => $range_min,
|
||||
'max' => $range_max
|
||||
);
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$url = 'https://api.leboncoin.fr/finder/search/';
|
||||
$data = $this->buildRequestJson();
|
||||
|
||||
$header = array(
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($data),
|
||||
'api_key: ' . self::$LBC_API_KEY
|
||||
);
|
||||
|
||||
$opts = array(
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => $data
|
||||
|
||||
);
|
||||
|
||||
$content = getContents($url, $header, $opts)
|
||||
or returnServerError('Could not request LeBonCoin. Tried: ' . $url);
|
||||
|
||||
$json = json_decode($content);
|
||||
|
||||
if($json->total === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tags = $list->find('li');
|
||||
foreach($json->ads as $element) {
|
||||
|
||||
foreach($tags as $element) {
|
||||
$item['title'] = $element->subject;
|
||||
$item['content'] = $element->body;
|
||||
$item['date'] = $element->index_date;
|
||||
$item['timestamp'] = strtotime($element->index_date);
|
||||
$item['uri'] = $element->url;
|
||||
$item['ad_type'] = $element->ad_type;
|
||||
$item['author'] = $element->owner->name;
|
||||
|
||||
$element = $element->find('a', 0);
|
||||
if(isset($element->location->city)) {
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $element->href;
|
||||
$title = html_entity_decode($element->getAttribute('title'));
|
||||
$content_image = $element->find('div.item_image', 0)->find('.lazyload', 0);
|
||||
$item['city'] = $element->location->city;
|
||||
$item['content'] .= ' -- ' . $element->location->city;
|
||||
|
||||
if($content_image !== null) {
|
||||
$content = '<img src="' . $content_image->getAttribute('data-imgsrc') . '" alt="thumbnail">';
|
||||
} else {
|
||||
$content = "";
|
||||
}
|
||||
$date = $element->find('aside.item_absolute', 0)->find('p.item_sup', 0);
|
||||
|
||||
$detailsList = $element->find('section.item_infos', 0);
|
||||
if(isset($element->location->zipcode)) {
|
||||
$item['zipcode'] = $element->location->zipcode;
|
||||
}
|
||||
|
||||
for($i = 0; $i <= 1; $i++) $content .= $detailsList->find('p.item_supp', $i)->plaintext;
|
||||
$price = $detailsList->find('h3.item_price', 0);
|
||||
$content .= $price === null ? '' : $price->plaintext;
|
||||
if(isset($element->price)) {
|
||||
|
||||
$item['price'] = $element->price[0];
|
||||
$item['content'] .= ' -- ' . current($element->price) . '€';
|
||||
|
||||
}
|
||||
|
||||
if(isset($element->images->urls)) {
|
||||
|
||||
$item['thumbnail'] = $element->images->thumb_url;
|
||||
$item['enclosures'] = array();
|
||||
|
||||
foreach($element->images->urls as $image) {
|
||||
$item['enclosures'][] = $image;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$item['title'] = $title;
|
||||
$item['content'] = $content . $date;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function buildRequestJson() {
|
||||
|
||||
$requestJson = new StdClass();
|
||||
$requestJson->owner_type = $this->getInput('owner');
|
||||
$requestJson->filters = new StdClass();
|
||||
|
||||
$requestJson->filters->keywords = array(
|
||||
'text' => $this->getInput('keywords')
|
||||
);
|
||||
|
||||
if($this->getInput('region') != '') {
|
||||
$requestJson->filters->location['regions'] = [$this->getInput('region')];
|
||||
}
|
||||
|
||||
if($this->getInput('department') != '') {
|
||||
$requestJson->filters->location['departments'] = [$this->getInput('department')];
|
||||
}
|
||||
|
||||
if($this->getInput('cities') != '') {
|
||||
|
||||
$requestJson->filters->location['city_zipcodes'] = array();
|
||||
|
||||
foreach (explode(',', $this->getInput('cities')) as $zipcode) {
|
||||
|
||||
$requestJson->filters->location['city_zipcodes'][] = array(
|
||||
'zipcode' => trim($zipcode)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$requestJson->filters->category = array(
|
||||
'id' => $this->getInput('category')
|
||||
);
|
||||
|
||||
if($this->getInput('pricemin') != ''
|
||||
|| $this->getInput('pricemax') != '') {
|
||||
|
||||
$requestJson->filters->ranges->price = $this->getRange(
|
||||
'price',
|
||||
$this->getInput('pricemin'),
|
||||
$this->getInput('pricemax')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if($this->getInput('estate') != '') {
|
||||
$requestJson->filters->enums['real_estate_type'] = [$this->getInput('estate')];
|
||||
}
|
||||
|
||||
if($this->getInput('roomsmin') != ''
|
||||
|| $this->getInput('roomsmax') != '') {
|
||||
|
||||
$requestJson->filters->ranges->rooms = $this->getRange(
|
||||
'rooms',
|
||||
$this->getInput('roomsmin'),
|
||||
$this->getInput('roomsmax')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if($this->getInput('squaremin') != ''
|
||||
|| $this->getInput('squaremax') != '') {
|
||||
|
||||
$requestJson->filters->ranges->square = $this->getRange(
|
||||
'square',
|
||||
$this->getInput('squaremin'),
|
||||
$this->getInput('squaremax')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if($this->getInput('mileagemin') != ''
|
||||
|| $this->getInput('mileagemax') != '') {
|
||||
|
||||
$requestJson->filters->ranges->mileage = $this->getRange(
|
||||
'mileage',
|
||||
$this->getInput('mileagemin'),
|
||||
$this->getInput('mileagemax')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if($this->getInput('yearmin') != ''
|
||||
|| $this->getInput('yearmax') != '') {
|
||||
|
||||
$requestJson->filters->ranges->regdate = $this->getRange(
|
||||
'year',
|
||||
$this->getInput('yearmin'),
|
||||
$this->getInput('yearmax')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if($this->getInput('cubiccapacitymin') != ''
|
||||
|| $this->getInput('cubiccapacitymax') != '') {
|
||||
|
||||
$requestJson->filters->ranges->cubic_capacity = $this->getRange(
|
||||
'cubic_capacity',
|
||||
$this->getInput('cubiccapacitymin'),
|
||||
$this->getInput('cubiccapacitymax')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if($this->getInput('fuel') != '') {
|
||||
$requestJson->filters->enums['fuel'] = [$this->getInput('fuel')];
|
||||
}
|
||||
|
||||
$requestJson->limit = 30;
|
||||
|
||||
return json_encode($requestJson);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -3,8 +3,7 @@ class LeMondeInformatiqueBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'Le Monde Informatique';
|
||||
const URI = 'http://www.lemondeinformatique.fr/';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const URI = 'https://www.lemondeinformatique.fr/';
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
public function collectData(){
|
||||
@@ -15,30 +14,26 @@ class LeMondeInformatiqueBridge extends FeedExpander {
|
||||
$item = parent::parseItem($newsItem);
|
||||
$article_html = getSimpleHTMLDOMCached($item['uri'])
|
||||
or returnServerError('Could not request LeMondeInformatique: ' . $item['uri']);
|
||||
$item['content'] = $this->cleanArticle($article_html->find('div#article', 0)->innertext);
|
||||
$item['title'] = $article_html->find('h1.cleanprint-title', 0)->plaintext;
|
||||
|
||||
//Deduce thumbnail URL from article image URL
|
||||
$item['enclosures'] = array(
|
||||
str_replace(
|
||||
'/grande/',
|
||||
'/petite/',
|
||||
$article_html->find('.article-image', 0)->find('img', 0)->src
|
||||
)
|
||||
);
|
||||
|
||||
//No response header sets the encoding, explicit conversion is needed or subsequent xml_encode() will fail
|
||||
$item['content'] = utf8_encode($this->cleanArticle($article_html->find('div.col-primary', 0)->innertext));
|
||||
$item['author'] = utf8_encode($article_html->find('div.author-infos', 0)->find('b', 0)->plaintext);
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function stripCDATA($string){
|
||||
$string = str_replace('<![CDATA[', '', $string);
|
||||
$string = str_replace(']]>', '', $string);
|
||||
return $string;
|
||||
}
|
||||
|
||||
private function stripWithDelimiters($string, $start, $end){
|
||||
while(strpos($string, $start) !== false) {
|
||||
$section_to_remove = substr($string, strpos($string, $start));
|
||||
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
|
||||
$string = str_replace($section_to_remove, '', $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
private function cleanArticle($article_html){
|
||||
$article_html = $this->stripWithDelimiters($article_html, '<script', '</script>');
|
||||
$article_html = $this->stripWithDelimiters($article_html, '<h1 class="cleanprint-title"', '</h1>');
|
||||
$article_html = stripWithDelimiters($article_html, '<script', '</script>');
|
||||
$article_html = explode('<p class="contact-error', $article_html)[0] . '</div>';
|
||||
return $article_html;
|
||||
}
|
||||
}
|
||||
|
@@ -38,6 +38,10 @@ class LegifranceJOBridge extends BridgeAbstract {
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.legifrance.gouv.fr/img/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or $this->returnServer('Unable to download ' . self::URI);
|
||||
|
@@ -3,7 +3,7 @@ class LesJoiesDuCodeBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'superbaillot.net';
|
||||
const NAME = 'Les Joies Du Code';
|
||||
const URI = 'http://lesjoiesducode.fr/';
|
||||
const URI = 'https://lesjoiesducode.fr/';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'LesJoiesDuCode';
|
||||
|
||||
@@ -22,20 +22,12 @@ class LesJoiesDuCodeBridge extends BridgeAbstract {
|
||||
// retrieve .gif instead of static .jpg
|
||||
$images = $temp->find('p img');
|
||||
foreach($images as $image) {
|
||||
$img_src = str_replace(".jpg", ".gif", $image->src);
|
||||
$img_src = str_replace('.jpg', '.gif', $image->src);
|
||||
$image->src = $img_src;
|
||||
}
|
||||
$content = $temp->innertext;
|
||||
|
||||
$auteur = $temp->find('i', 0);
|
||||
$pos = strpos($auteur->innertext, "by");
|
||||
|
||||
if($pos > 0) {
|
||||
$auteur = trim(str_replace("*/", "", substr($auteur->innertext, ($pos + 2))));
|
||||
$item['author'] = $auteur;
|
||||
}
|
||||
|
||||
$item['content'] .= trim($content);
|
||||
$item['content'] = trim($content);
|
||||
$item['uri'] = $url;
|
||||
$item['title'] = trim($titre);
|
||||
|
||||
|
@@ -100,7 +100,7 @@ class MangareaderBridge extends BridgeAbstract {
|
||||
case 'Get popular mangas':
|
||||
// Find manga name within "Popular mangas for ..."
|
||||
$pagetitle = $xpath->query(".//*[@id='bodyalt']/h1")->item(0)->nodeValue;
|
||||
$this->request = substr($pagetitle, 0, strrpos($pagetitle, " -"));
|
||||
$this->request = substr($pagetitle, 0, strrpos($pagetitle, ' -'));
|
||||
$this->getPopularMangas($xpath);
|
||||
break;
|
||||
case 'Get manga updates':
|
||||
@@ -120,7 +120,7 @@ class MangareaderBridge extends BridgeAbstract {
|
||||
// Return some dummy-data if no content available
|
||||
if(empty($this->items)) {
|
||||
$item = array();
|
||||
$item['content'] = "<p>No updates available</p>";
|
||||
$item['content'] = '<p>No updates available</p>';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
@@ -143,18 +143,18 @@ class MangareaderBridge extends BridgeAbstract {
|
||||
$item['title'] = htmlspecialchars($manga->nodeValue);
|
||||
|
||||
// Add each chapter to the feed
|
||||
$item['content'] = "";
|
||||
$item['content'] = '';
|
||||
|
||||
foreach ($chapters as $chapter) {
|
||||
if($item['content'] <> "") {
|
||||
$item['content'] .= "<br>";
|
||||
if($item['content'] <> '') {
|
||||
$item['content'] .= '<br>';
|
||||
}
|
||||
$item['content'] .= "<a href='"
|
||||
. self::URI
|
||||
. htmlspecialchars($chapter->getAttribute('href'))
|
||||
. "'>"
|
||||
. htmlspecialchars($chapter->nodeValue)
|
||||
. "</a>";
|
||||
. '</a>';
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
@@ -211,13 +211,13 @@ EOD;
|
||||
|
||||
foreach ($chapters as $chapter) {
|
||||
$item = array();
|
||||
$item['title'] = htmlspecialchars($xpath->query("td[1]", $chapter)
|
||||
$item['title'] = htmlspecialchars($xpath->query('td[1]', $chapter)
|
||||
->item(0)
|
||||
->nodeValue);
|
||||
$item['uri'] = self::URI . $xpath->query("td[1]/a", $chapter)
|
||||
$item['uri'] = self::URI . $xpath->query('td[1]/a', $chapter)
|
||||
->item(0)
|
||||
->getAttribute('href');
|
||||
$item['timestamp'] = strtotime($xpath->query("td[2]", $chapter)
|
||||
$item['timestamp'] = strtotime($xpath->query('td[2]', $chapter)
|
||||
->item(0)
|
||||
->nodeValue);
|
||||
array_unshift($this->items, $item);
|
||||
@@ -227,12 +227,12 @@ EOD;
|
||||
public function getURI(){
|
||||
switch($this->queriedContext) {
|
||||
case 'Get latest updates':
|
||||
$path = "latest";
|
||||
$path = 'latest';
|
||||
break;
|
||||
case 'Get popular mangas':
|
||||
$path = "popular";
|
||||
if($this->getInput('category') !== "all") {
|
||||
$path .= "/" . $this->getInput('category');
|
||||
$path = 'popular';
|
||||
if($this->getInput('category') !== 'all') {
|
||||
$path .= '/' . $this->getInput('category');
|
||||
}
|
||||
break;
|
||||
case 'Get manga updates':
|
||||
|
144
bridges/MydealsBridge.php
Normal file
144
bridges/MydealsBridge.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
require_once(__DIR__ . '/DealabsBridge.php');
|
||||
class MydealsBridge extends PepperBridgeAbstract {
|
||||
|
||||
const NAME = 'Mydeals bridge';
|
||||
const URI = 'https://www.mydealz.de/';
|
||||
const DESCRIPTION = 'Zeigt die Deals von mydeals.de';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
const PARAMETERS = array(
|
||||
'Suche nach Stichworten' => array (
|
||||
'q' => array(
|
||||
'name' => 'Stichworten',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
),
|
||||
'hide_expired' => array(
|
||||
'name' => 'Abgelaufenes ausblenden',
|
||||
'type' => 'checkbox',
|
||||
'required' => 'true'
|
||||
),
|
||||
'hide_local' => array(
|
||||
'name' => 'Lokales ausblenden',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Deals im physischen Geschäft ausblenden',
|
||||
'required' => 'true'
|
||||
),
|
||||
'priceFrom' => array(
|
||||
'name' => 'Minimaler Preis',
|
||||
'type' => 'text',
|
||||
'title' => 'Minmaler Preis in Euros',
|
||||
'required' => 'false',
|
||||
'defaultValue' => ''
|
||||
),
|
||||
'priceTo' => array(
|
||||
'name' => 'Maximaler Preis',
|
||||
'type' => 'text',
|
||||
'title' => 'maximaler Preis in Euro',
|
||||
'required' => 'false',
|
||||
'defaultValue' => ''
|
||||
),
|
||||
),
|
||||
|
||||
'Deals pro Gruppen' => array(
|
||||
'group' => array(
|
||||
'name' => 'Gruppen',
|
||||
'type' => 'list',
|
||||
'required' => 'true',
|
||||
'title' => 'Gruppe, deren Deals angezeigt werden müssen',
|
||||
'values' => array(
|
||||
'Elektronik' => 'elektronik',
|
||||
'Handy & Smartphone' => 'smartphone',
|
||||
'Gaming' => 'gaming',
|
||||
'Software' => 'apps-software',
|
||||
'Fashion Frauen' => 'fashion-frauen',
|
||||
'Fashion Männer' => 'fashion-accessoires',
|
||||
'Beauty & Gesundheit' => 'beauty',
|
||||
'Family & Kids' => 'family-kids',
|
||||
'Essen & Trinken' => 'food',
|
||||
'Freizeit & Reisen' => 'reisen',
|
||||
'Haushalt & Garten' => 'home-living',
|
||||
'Entertainment' => 'entertainment',
|
||||
'Verträge & Finanzen' => 'vertraege-finanzen',
|
||||
'Coupons' => 'coupons',
|
||||
|
||||
)
|
||||
),
|
||||
'order' => array(
|
||||
'name' => 'sortieren nach',
|
||||
'type' => 'list',
|
||||
'required' => 'true',
|
||||
'title' => 'Sortierung der deals',
|
||||
'values' => array(
|
||||
'Vom heißesten zum kältesten Deal' => '',
|
||||
'Vom jüngsten Deal zum ältesten' => '-new',
|
||||
'Vom am meisten kommentierten Deal zum am wenigsten kommentierten Deal' => '-discussed'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public $lang = array(
|
||||
'bridge-uri' => SELF::URI,
|
||||
'bridge-name' => SELF::NAME,
|
||||
'context-keyword' => 'Suche nach Stichworten',
|
||||
'context-group' => 'Deals pro Gruppen',
|
||||
'uri-group' => '/gruppe/',
|
||||
'request-error' => 'Could not request mydeals',
|
||||
'no-results' => 'Ups, wir konnten keine Deals zu',
|
||||
'relative-date-indicator' => array(
|
||||
'vor',
|
||||
'seit'
|
||||
),
|
||||
'price' => 'Preis',
|
||||
'shipping' => 'Versand',
|
||||
'origin' => 'Ursprung',
|
||||
'discount' => 'Rabatte',
|
||||
'title-keyword' => 'Suche',
|
||||
'title-group' => 'Gruppe',
|
||||
'local-months' => array(
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mär',
|
||||
'Apr',
|
||||
'Mai',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Okt',
|
||||
'Nov',
|
||||
'Dez',
|
||||
'.'
|
||||
),
|
||||
'local-time-relative' => array(
|
||||
'eingestellt vor ',
|
||||
'm',
|
||||
'h,',
|
||||
'day',
|
||||
'days',
|
||||
'month',
|
||||
'year',
|
||||
'and '
|
||||
),
|
||||
'date-prefixes' => array(
|
||||
'eingestellt am ',
|
||||
'lokal ',
|
||||
'aktualisiert ',
|
||||
),
|
||||
'relative-date-alt-prefixes' => array(
|
||||
'aktualisiert vor ',
|
||||
'kommentiert vor ',
|
||||
'heiß seit '
|
||||
),
|
||||
'relative-date-ignore-suffix' => array(
|
||||
'/von.*$/'
|
||||
),
|
||||
'localdeal' => array(
|
||||
'Lokal ',
|
||||
'Läuft bis '
|
||||
)
|
||||
);
|
||||
|
||||
}
|
@@ -12,7 +12,7 @@ class NasaApodBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(self::URI . 'archivepix.html')
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
||||
$list = explode("<br>", $html->find('b', 0)->innertext);
|
||||
$list = explode('<br>', $html->find('b', 0)->innertext);
|
||||
|
||||
for($i = 0; $i < 3; $i++) {
|
||||
$line = $list[$i];
|
||||
@@ -32,7 +32,7 @@ class NasaApodBridge extends BridgeAbstract {
|
||||
$explanation = $picture_html->find('p', 2)->innertext;
|
||||
|
||||
//Extract date from the picture page
|
||||
$date = explode(" ", $picture_html->find('p', 1)->innertext);
|
||||
$date = explode(' ', $picture_html->find('p', 1)->innertext);
|
||||
$item['timestamp'] = strtotime($date[4] . $date[3] . $date[2]);
|
||||
|
||||
//Other informations
|
||||
|
@@ -6,16 +6,6 @@ class NeuviemeArtBridge extends FeedExpander {
|
||||
const URI = 'http://www.9emeart.fr/';
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
private function stripWithDelimiters($string, $start, $end){
|
||||
while(strpos($string, $start) !== false) {
|
||||
$section_to_remove = substr($string, strpos($string, $start));
|
||||
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
|
||||
$string = str_replace($section_to_remove, '', $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
protected function parseItem($item){
|
||||
$item = parent::parseItem($item);
|
||||
|
||||
@@ -34,16 +24,16 @@ class NeuviemeArtBridge extends FeedExpander {
|
||||
}
|
||||
|
||||
$article_content = '';
|
||||
if($article_image) {
|
||||
if ($article_image) {
|
||||
$article_content = '<p><img src="' . $article_image . '" /></p>';
|
||||
}
|
||||
$article_content .= str_replace(
|
||||
'src="/', 'src="' . self::URI,
|
||||
$article_html->find('div.newsGenerique_con', 0)->innertext
|
||||
);
|
||||
$article_content = $this->stripWithDelimiters($article_content, '<script', '</script>');
|
||||
$article_content = $this->stripWithDelimiters($article_content, '<style', '</style>');
|
||||
$article_content = $this->stripWithDelimiters($article_content, '<link', '>');
|
||||
$article_content = stripWithDelimiters($article_content, '<script', '</script>');
|
||||
$article_content = stripWithDelimiters($article_content, '<style', '</style>');
|
||||
$article_content = stripWithDelimiters($article_content, '<link', '>');
|
||||
|
||||
$item['content'] = $article_content;
|
||||
|
||||
|
@@ -6,29 +6,105 @@ class NextInpactBridge extends FeedExpander {
|
||||
const URI = 'https://www.nextinpact.com/';
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'feed' => array(
|
||||
'name' => 'Feed',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Tous nos articles' => 'news',
|
||||
'Nos contenus en accès libre' => 'acces-libre',
|
||||
'Blog' => 'blog',
|
||||
'Bons plans' => 'bonsplans'
|
||||
)
|
||||
),
|
||||
'filter_premium' => array(
|
||||
'name' => 'Premium',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'No filter' => '0',
|
||||
'Hide Premium' => '1',
|
||||
'Only Premium' => '2'
|
||||
)
|
||||
),
|
||||
'filter_brief' => array(
|
||||
'name' => 'Brief',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'No filter' => '0',
|
||||
'Hide Brief' => '1',
|
||||
'Only Brief' => '2'
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas(self::URI . 'rss/news.xml', 10);
|
||||
$feed = $this->getInput('feed');
|
||||
if (empty($feed))
|
||||
$feed = 'news';
|
||||
$this->collectExpandableDatas(self::URI . 'rss/' . $feed . '.xml');
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
$item['content'] = $this->extractContent($item['uri']);
|
||||
$item['content'] = $this->extractContent($item, $item['uri']);
|
||||
if (is_null($item['content']))
|
||||
return null; //Filtered article
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function extractContent($url){
|
||||
$html2 = getSimpleHTMLDOMCached($url);
|
||||
$text = '<p><em>'
|
||||
. $html2->find('span.sub_title', 0)->innertext
|
||||
. '</em></p><p><img src="'
|
||||
. $html2->find('div.container_main_image_article', 0)->find('img.dedicated', 0)->src
|
||||
. '" alt="-" /></p><div>'
|
||||
. $html2->find('div[itemprop=articleBody]', 0)->innertext
|
||||
. '</div>';
|
||||
private function extractContent($item, $url){
|
||||
$html = getSimpleHTMLDOMCached($url);
|
||||
if (!is_object($html))
|
||||
return 'Failed to request NextInpact: ' . $url;
|
||||
|
||||
foreach(array(
|
||||
'filter_premium' => 'h2.title_reserve_article',
|
||||
'filter_brief' => 'div.brief-inner-content'
|
||||
) as $param_name => $selector) {
|
||||
$param_val = intval($this->getInput($param_name));
|
||||
if ($param_val != 0) {
|
||||
$element_present = is_object($html->find($selector, 0));
|
||||
$element_wanted = ($param_val == 2);
|
||||
if ($element_present != $element_wanted) {
|
||||
return null; //Filter article
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_object($html->find('div[itemprop=articleBody], div.brief-inner-content', 0))) {
|
||||
|
||||
$subtitle = trim($html->find('span.sub_title, div.brief-head', 0));
|
||||
if(is_object($subtitle) && $subtitle->plaintext !== $item['title']) {
|
||||
$subtitle = '<p><em>' . $subtitle->plaintext . '</em></p>';
|
||||
} else {
|
||||
$subtitle = '';
|
||||
}
|
||||
|
||||
$postimg = $html->find(
|
||||
'div.container_main_image_article, div.image-brief-container, div.image-brief-side-container', 0
|
||||
);
|
||||
if(is_object($postimg)) {
|
||||
$postimg = '<p><img src="'
|
||||
. $postimg->find('img.dedicated', 0)->src
|
||||
. '" alt="-" /></p>';
|
||||
} else {
|
||||
$postimg = '';
|
||||
}
|
||||
|
||||
$text = $subtitle
|
||||
. $postimg
|
||||
. $html->find('div[itemprop=articleBody], div.brief-inner-content', 0)->outertext;
|
||||
|
||||
} else {
|
||||
$text = $item['content']
|
||||
. '<p><em>Failed retrieve full article content</em></p>';
|
||||
}
|
||||
|
||||
$premium_article = $html->find('h2.title_reserve_article', 0);
|
||||
if (is_object($premium_article)) {
|
||||
$text .= '<p><em>' . $premium_article->innertext . '</em></p>';
|
||||
}
|
||||
|
||||
$premium_article = $html2->find('h2.title_reserve_article', 0);
|
||||
if (is_object($premium_article))
|
||||
$text = $text . '<p><em>' . $premium_article->innertext . '</em></p>';
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
@@ -32,43 +32,39 @@ class NextgovBridge extends FeedExpander {
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
|
||||
$item['content'] = '';
|
||||
$article_thumbnail = 'https://cdn.nextgov.com/nextgov/images/logo.png';
|
||||
$item['content'] = '<p><b>' . $item['content'] . '</b></p>';
|
||||
|
||||
$namespaces = $newsItem->getNamespaces(true);
|
||||
if(isset($namespaces['media'])) {
|
||||
$media = $newsItem->children($namespaces['media']);
|
||||
if(isset($media->content)) {
|
||||
$attributes = $media->content->attributes();
|
||||
$item['content'] = '<img src="' . $attributes['url'] . '">';
|
||||
$item['content'] = '<p><img src="' . $attributes['url'] . '"></p>' . $item['content'];
|
||||
$article_thumbnail = str_replace(
|
||||
'large.jpg',
|
||||
'small.jpg',
|
||||
strval($attributes['url'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$item['enclosures'] = array($article_thumbnail);
|
||||
$item['content'] .= $this->extractContent($item['uri']);
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function stripWithDelimiters($string, $start, $end){
|
||||
while (strpos($string, $start) !== false) {
|
||||
$section_to_remove = substr($string, strpos($string, $start));
|
||||
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
|
||||
$string = str_replace($section_to_remove, '', $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
private function extractContent($url){
|
||||
$article = getSimpleHTMLDOMCached($url)
|
||||
or returnServerError('Could not request Nextgov: ' . $url);
|
||||
$article = getSimpleHTMLDOMCached($url);
|
||||
|
||||
$contents = $article->find('div.wysiwyg', 0)->innertext;
|
||||
$contents = $this->stripWithDelimiters($contents, '<div class="ad-container">', '</div>');
|
||||
$contents = $this->stripWithDelimiters($contents, '<div', '</div>'); //ad outer div
|
||||
return $this->stripWithDelimiters($contents, '<script', '</script>');
|
||||
$contents = ($article_thumbnail == '' ? '' : '<p><img src="' . $article_thumbnail . '" /></p>')
|
||||
. '<p><b>'
|
||||
. $article_subtitle
|
||||
. '</b></p>'
|
||||
. trim($contents);
|
||||
if (!is_object($article))
|
||||
return 'Could not request Nextgov: ' . $url;
|
||||
|
||||
$contents = $article->find('div.wysiwyg', 0);
|
||||
$contents->find('svg.content-tombstone', 0)->outertext = '';
|
||||
$contents = $contents->innertext;
|
||||
$contents = stripWithDelimiters($contents, '<div class="ad-container">', '</div>');
|
||||
$contents = stripWithDelimiters($contents, '<div', '</div>'); //ad outer div
|
||||
return trim(stripWithDelimiters($contents, '<script', '</script>'));
|
||||
}
|
||||
}
|
||||
|
331
bridges/NineGagBridge.php
Normal file
331
bridges/NineGagBridge.php
Normal file
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
|
||||
class NineGagBridge extends BridgeAbstract {
|
||||
const NAME = '9gag Bridge';
|
||||
const URI = 'https://9gag.com/';
|
||||
const DESCRIPTION = 'Returns latest quotes from 9gag.';
|
||||
const MAINTAINER = 'ZeNairolf';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const PARAMETERS = array(
|
||||
'Popular' => array(
|
||||
'd' => array(
|
||||
'name' => 'Section',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'values' => array(
|
||||
'Hot' => 'hot',
|
||||
'Trending' => 'trending',
|
||||
'Fresh' => 'fresh',
|
||||
),
|
||||
),
|
||||
'p' => array(
|
||||
'name' => 'Pages',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 3,
|
||||
),
|
||||
),
|
||||
'Sections' => array(
|
||||
'g' => array(
|
||||
'name' => 'Section',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'values' => array(
|
||||
'Animals' => 'cute',
|
||||
'Anime & Manga' => 'anime-manga',
|
||||
'Ask 9GAG' => 'ask9gag',
|
||||
'Awesome' => 'awesome',
|
||||
'Basketball' => 'basketball',
|
||||
'Car' => 'car',
|
||||
'Classical Art Memes' => 'classicalartmemes',
|
||||
'Comic' => 'comic',
|
||||
'Cosplay' => 'cosplay',
|
||||
'Countryballs' => 'country',
|
||||
'DIY & Crafts' => 'imadedis',
|
||||
'Drawing & Illustration' => 'drawing',
|
||||
'Fan Art' => 'animefanart',
|
||||
'Food & Drinks' => 'food',
|
||||
'Football' => 'football',
|
||||
'Fortnite' => 'fortnite',
|
||||
'Funny' => 'funny',
|
||||
'GIF' => 'gif',
|
||||
'Gaming' => 'gaming',
|
||||
'Girl' => 'girl',
|
||||
'Girly Things' => 'girly',
|
||||
'Guy' => 'guy',
|
||||
'History' => 'history',
|
||||
'Home Design' => 'home',
|
||||
'Horror' => 'horror',
|
||||
'K-Pop' => 'kpop',
|
||||
'LEGO' => 'lego',
|
||||
'League of Legends' => 'leagueoflegends',
|
||||
'Movie & TV' => 'movie-tv',
|
||||
'Music' => 'music',
|
||||
'NFK - Not For Kids' => 'nsfw',
|
||||
'Overwatch' => 'overwatch',
|
||||
'PC Master Race' => 'pcmr',
|
||||
'PUBG' => 'pubg',
|
||||
'Pic Of The Day' => 'photography',
|
||||
'Pokémon' => 'pokemon',
|
||||
'Politics' => 'politics',
|
||||
'Relationship' => 'relationship',
|
||||
'Roast Me' => 'roastme',
|
||||
'Satisfying' => 'satisfying',
|
||||
'Savage' => 'savage',
|
||||
'School' => 'school',
|
||||
'Sci-Tech' => 'science',
|
||||
'Sport' => 'sport',
|
||||
'Star Wars' => 'starwars',
|
||||
'Superhero' => 'superhero',
|
||||
'Surreal Memes' => 'surrealmemes',
|
||||
'Timely' => 'timely',
|
||||
'Travel' => 'travel',
|
||||
'Video' => 'video',
|
||||
'WTF' => 'wtf',
|
||||
'Wallpaper' => 'wallpaper',
|
||||
'Warhammer' => 'warhammer',
|
||||
),
|
||||
),
|
||||
't' => array(
|
||||
'name' => 'Type',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'values' => array(
|
||||
'Hot' => 'hot',
|
||||
'Fresh' => 'fresh',
|
||||
),
|
||||
),
|
||||
'p' => array(
|
||||
'name' => 'Pages',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 3,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const MIN_NBR_PAGE = 1;
|
||||
const MAX_NBR_PAGE = 6;
|
||||
|
||||
protected $p = null;
|
||||
|
||||
public function collectData() {
|
||||
$url = sprintf(
|
||||
'%sv1/group-posts/group/%s/type/%s?',
|
||||
self::URI,
|
||||
$this->getGroup(),
|
||||
$this->getType()
|
||||
);
|
||||
$cursor = 'c=10';
|
||||
$posts = array();
|
||||
for ($i = 0; $i < $this->getPages(); ++$i) {
|
||||
$content = getContents($url . $cursor);
|
||||
$json = json_decode($content, true);
|
||||
$posts = array_merge($posts, $json['data']['posts']);
|
||||
$cursor = $json['data']['nextCursor'];
|
||||
}
|
||||
|
||||
foreach ($posts as $post) {
|
||||
$item['uri'] = $post['url'];
|
||||
$item['title'] = $post['title'];
|
||||
$item['content'] = self::getContent($post);
|
||||
$item['categories'] = self::getCategories($post);
|
||||
$item['timestamp'] = self::getTimestamp($post);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if ($this->getInput('d')) {
|
||||
$name = sprintf('%s - %s', '9GAG', $this->getParameterKey('d'));
|
||||
} elseif ($this->getInput('g')) {
|
||||
$name = sprintf('%s - %s', '9GAG', $this->getParameterKey('g'));
|
||||
if ($this->getInput('t')) {
|
||||
$name = sprintf('%s [%s]', $name, $this->getParameterKey('t'));
|
||||
}
|
||||
}
|
||||
if (!empty($name)) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = $this->getInput('g');
|
||||
if ($uri === 'default') {
|
||||
$uri = $this->getInput('t');
|
||||
}
|
||||
|
||||
return self::URI . $uri;
|
||||
}
|
||||
|
||||
protected function getGroup() {
|
||||
if ($this->getInput('d')) {
|
||||
return 'default';
|
||||
}
|
||||
|
||||
return $this->getInput('g');
|
||||
}
|
||||
|
||||
protected function getType() {
|
||||
if ($this->getInput('d')) {
|
||||
return $this->getInput('d');
|
||||
}
|
||||
|
||||
return $this->getInput('t');
|
||||
}
|
||||
|
||||
protected function getPages() {
|
||||
if ($this->p === null) {
|
||||
$value = (int) $this->getInput('p');
|
||||
$value = ($value < self::MIN_NBR_PAGE) ? self::MIN_NBR_PAGE : $value;
|
||||
$value = ($value > self::MAX_NBR_PAGE) ? self::MAX_NBR_PAGE : $value;
|
||||
|
||||
$this->p = $value;
|
||||
}
|
||||
|
||||
return $this->p;
|
||||
}
|
||||
|
||||
protected function getParameterKey($input = '') {
|
||||
$params = $this->getParameters();
|
||||
$tab = 'Sections';
|
||||
if ($input === 'd') {
|
||||
$tab = 'Popular';
|
||||
}
|
||||
if (!isset($params[$tab][$input])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return array_search(
|
||||
$this->getInput($input),
|
||||
$params[$tab][$input]['values']
|
||||
);
|
||||
}
|
||||
|
||||
protected static function getContent($post) {
|
||||
if ($post['type'] === 'Animated') {
|
||||
$content = self::getAnimated($post);
|
||||
} elseif ($post['type'] === 'Article') {
|
||||
$content = self::getArticle($post);
|
||||
} else {
|
||||
$content = self::getPhoto($post);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected static function getPhoto($post) {
|
||||
$image = $post['images']['image460'];
|
||||
$photo = '<picture>';
|
||||
$photo .= sprintf(
|
||||
'<source srcset="%s" type="image/webp">',
|
||||
$image['webpUrl']
|
||||
);
|
||||
$photo .= sprintf(
|
||||
'<img src="%s" alt="%s" %s>',
|
||||
$image['url'],
|
||||
$post['title'],
|
||||
'width="500"'
|
||||
);
|
||||
$photo .= '</picture>';
|
||||
|
||||
return $photo;
|
||||
}
|
||||
|
||||
protected static function getAnimated($post) {
|
||||
$poster = $post['images']['image460']['url'];
|
||||
$sources = $post['images'];
|
||||
$video = sprintf(
|
||||
'<video poster="%s" %s>',
|
||||
$poster,
|
||||
'preload="auto" loop controls style="min-height: 300px" width="500"'
|
||||
);
|
||||
$video .= sprintf(
|
||||
'<source src="%s" type="video/webm">',
|
||||
$sources['image460sv']['vp9Url']
|
||||
);
|
||||
$video .= sprintf(
|
||||
'<source src="%s" type="video/mp4">',
|
||||
$sources['image460sv']['h265Url']
|
||||
);
|
||||
$video .= sprintf(
|
||||
'<source src="%s" type="video/mp4">',
|
||||
$sources['image460svwm']['url']
|
||||
);
|
||||
$video .= '</video>';
|
||||
|
||||
return $video;
|
||||
}
|
||||
|
||||
protected static function getArticle($post) {
|
||||
$blocks = $post['article']['blocks'];
|
||||
$medias = $post['article']['medias'];
|
||||
$contents = array();
|
||||
foreach ($blocks as $block) {
|
||||
if ('Media' === $block['type']) {
|
||||
$mediaId = $block['mediaId'];
|
||||
$contents[] = self::getContent($medias[$mediaId]);
|
||||
} elseif ('RichText' === $block['type']) {
|
||||
$contents[] = self::getRichText($block['content']);
|
||||
}
|
||||
}
|
||||
|
||||
$content = join('</div><div>', $contents);
|
||||
$content = sprintf(
|
||||
'<%1$s>%2$s</%1$s>',
|
||||
'div',
|
||||
$content
|
||||
);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected static function getRichText($text = '') {
|
||||
$text = trim($text);
|
||||
|
||||
if (preg_match('/^>\s(?<text>.*)/', $text, $matches)) {
|
||||
$text = sprintf(
|
||||
'<%1$s>%2$s</%1$s>',
|
||||
'blockquote',
|
||||
$matches['text']
|
||||
);
|
||||
} else {
|
||||
$text = sprintf(
|
||||
'<%1$s>%2$s</%1$s>',
|
||||
'p',
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
protected static function getCategories($post) {
|
||||
$params = self::PARAMETERS;
|
||||
$sections = $params['Sections']['g']['values'];
|
||||
|
||||
if(isset($post['sections'])) {
|
||||
$postSections = $post['sections'];
|
||||
} elseif (isset($post['postSection'])) {
|
||||
$postSections = array($post['postSection']);
|
||||
} else {
|
||||
$postSections = array();
|
||||
}
|
||||
|
||||
foreach ($postSections as $key => $section) {
|
||||
$postSections[$key] = array_search($section, $sections);
|
||||
}
|
||||
|
||||
return $postSections;
|
||||
}
|
||||
|
||||
protected static function getTimestamp($post) {
|
||||
$url = $post['images']['image460']['url'];
|
||||
$headers = get_headers($url, true);
|
||||
$date = $headers['Date'];
|
||||
$time = strtotime($date);
|
||||
|
||||
return $time;
|
||||
}
|
||||
}
|
61
bridges/NotAlwaysBridge.php
Normal file
61
bridges/NotAlwaysBridge.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
class NotAlwaysBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'mozes';
|
||||
const NAME = 'Not Always family Bridge';
|
||||
const URI = 'https://notalwaysright.com/';
|
||||
const DESCRIPTION = 'Returns the latest stories';
|
||||
const CACHE_TIMEOUT = 1800; // 30 minutes
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'filter' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Filter',
|
||||
'values' => array(
|
||||
'All' => 'all',
|
||||
'Right' => 'right',
|
||||
'Working' => 'working',
|
||||
'Romantic' => 'romantic',
|
||||
'Related' => 'related',
|
||||
'Learning' => 'learning',
|
||||
'Friendly' => 'friendly',
|
||||
'Hopeless' => 'hopeless',
|
||||
'Unfiltered' => 'unfiltered'
|
||||
),
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'favicon_nar.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request NotAlways.');
|
||||
foreach($html->find('.post') as $post) {
|
||||
#print_r($post);
|
||||
$item = array();
|
||||
$item['uri'] = $post->find('h1', 0)->find('a', 0)->href;
|
||||
$item['content'] = $post;
|
||||
$item['title'] = $post->find('h1', 0)->find('a', 0)->innertext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('filter'))) {
|
||||
return $this->getInput('filter') . ' - NotAlways Bridge';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('filter'))) {
|
||||
return self::URI . $this->getInput('filter') . '/';
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
131
bridges/NyaaTorrentsBridge.php
Normal file
131
bridges/NyaaTorrentsBridge.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
class NyaaTorrentsBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'NyaaTorrents';
|
||||
const URI = 'https://nyaa.si/';
|
||||
const DESCRIPTION = 'Returns the newest torrents, with optional search criteria.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'f' => array(
|
||||
'name' => 'Filter',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'No filter' => '0',
|
||||
'No remakes' => '1',
|
||||
'Trusted only' => '2'
|
||||
)
|
||||
),
|
||||
'c' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All categories' => '0_0',
|
||||
'Anime' => '1_0',
|
||||
'Anime - AMV' => '1_1',
|
||||
'Anime - English' => '1_2',
|
||||
'Anime - Non-English' => '1_3',
|
||||
'Anime - Raw' => '1_4',
|
||||
'Audio' => '2_0',
|
||||
'Audio - Lossless' => '2_1',
|
||||
'Audio - Lossy' => '2_2',
|
||||
'Literature' => '3_0',
|
||||
'Literature - English' => '3_1',
|
||||
'Literature - Non-English' => '3_2',
|
||||
'Literature - Raw' => '3_3',
|
||||
'Live Action' => '4_0',
|
||||
'Live Action - English' => '4_1',
|
||||
'Live Action - Idol/PV' => '4_2',
|
||||
'Live Action - Non-English' => '4_3',
|
||||
'Live Action - Raw' => '4_4',
|
||||
'Pictures' => '5_0',
|
||||
'Pictures - Graphics' => '5_1',
|
||||
'Pictures - Photos' => '5_2',
|
||||
'Software' => '6_0',
|
||||
'Software - Apps' => '6_1',
|
||||
'Software - Games' => '6_2',
|
||||
)
|
||||
),
|
||||
'q' => array(
|
||||
'name' => 'Keyword',
|
||||
'description' => 'Keyword(s)',
|
||||
'type' => 'text'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'static/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
// Build Search URL from user-provided parameters
|
||||
$search_url = self::URI . '?s=id&o=desc&'
|
||||
. http_build_query(array(
|
||||
'f' => $this->getInput('f'),
|
||||
'c' => $this->getInput('c'),
|
||||
'q' => $this->getInput('q')
|
||||
));
|
||||
|
||||
// Retrieve torrent listing from search results, which does not contain torrent description
|
||||
$html = getSimpleHTMLDOM($search_url)
|
||||
or returnServerError('Could not request Nyaa: ' . $search_url);
|
||||
$links = $html->find('a');
|
||||
$results = array();
|
||||
foreach ($links as $link)
|
||||
if (strpos($link->href, '/view/') === 0 && !in_array($link->href, $results))
|
||||
$results[] = $link->href;
|
||||
if (empty($results) && empty($this->getInput('q')))
|
||||
returnServerError('No results from Nyaa: ' . $url, 500);
|
||||
|
||||
//Process each item individually
|
||||
foreach ($results as $element) {
|
||||
|
||||
//Limit total amount of requests
|
||||
if(count($this->items) >= 20) {
|
||||
break;
|
||||
}
|
||||
|
||||
$torrent_id = str_replace('/view/', '', $element);
|
||||
|
||||
//Ignore entries without valid torrent ID
|
||||
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
|
||||
|
||||
//Retrieve data for this torrent ID
|
||||
$item_uri = self::URI . 'view/' . $torrent_id;
|
||||
|
||||
//Retrieve full description from torrent page
|
||||
if ($item_html = getSimpleHTMLDOMCached($item_uri)) {
|
||||
|
||||
//Retrieve data from page contents
|
||||
$item_title = str_replace(' :: Nyaa', '', $item_html->find('title', 0)->plaintext);
|
||||
$item_desc = str_get_html(markdownToHtml($item_html->find('#torrent-description', 0)->innertext));
|
||||
$item_author = extractFromDelimiters($item_html->outertext, 'href="/user/', '"');
|
||||
$item_date = intval(extractFromDelimiters($item_html->outertext, 'data-timestamp="', '"'));
|
||||
|
||||
//Retrieve image for thumbnail or generic logo fallback
|
||||
$item_image = $this->getURI() . 'static/img/avatar/default.png';
|
||||
foreach ($item_desc->find('img') as $img) {
|
||||
if (strpos($img->src, 'prez') === false) {
|
||||
$item_image = $img->src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Build and add final item
|
||||
$item = array();
|
||||
$item['uri'] = $item_uri;
|
||||
$item['title'] = $item_title;
|
||||
$item['author'] = $item_author;
|
||||
$item['timestamp'] = $item_date;
|
||||
$item['enclosures'] = array($item_image);
|
||||
$item['content'] = $item_desc;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
$element = null;
|
||||
}
|
||||
$results = null;
|
||||
}
|
||||
}
|
100
bridges/PikabuBridge.php
Normal file
100
bridges/PikabuBridge.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
class PikabuBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Пикабу';
|
||||
const URI = 'https://pikabu.ru';
|
||||
const DESCRIPTION = 'Выводит посты по тегу';
|
||||
const MAINTAINER = 'em92';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'По тегу' => array(
|
||||
'tag' => array(
|
||||
'name' => 'Тег',
|
||||
'exampleValue' => 'it',
|
||||
'required' => true
|
||||
),
|
||||
'filter' => array(
|
||||
'name' => 'Фильтр',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Горячее' => 'hot',
|
||||
'Свежее' => 'new',
|
||||
),
|
||||
'defaultValue' => 'hot'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getURI() {
|
||||
if ($this->getInput('tag')) {
|
||||
return self::URI . '/tag/' . rawurlencode($this->getInput('tag')) . '/' . rawurlencode($this->getInput('filter'));
|
||||
} else {
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://cs.pikabu.ru/assets/favicon.ico';
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if (is_string($this->getInput('tag'))) {
|
||||
return $this->getInput('tag') . ' - ' . parent::getName();
|
||||
} else {
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$link = $this->getURI();
|
||||
|
||||
$text_html = getContents($link) or returnServerError('Could not fetch ' . $link);
|
||||
$text_html = iconv('windows-1251', 'utf-8', $text_html);
|
||||
$html = str_get_html($text_html);
|
||||
|
||||
foreach($html->find('article.story') as $post) {
|
||||
$time = $post->find('time.story__datetime', 0);
|
||||
if (is_null($time)) continue;
|
||||
|
||||
$el_to_remove_selectors = array(
|
||||
'.story__read-more',
|
||||
'svg.story-image__stretch',
|
||||
);
|
||||
|
||||
foreach($el_to_remove_selectors as $el_to_remove_selector) {
|
||||
foreach($post->find($el_to_remove_selector) as $el) {
|
||||
$el->outertext = '';
|
||||
}
|
||||
}
|
||||
|
||||
foreach($post->find('img') as $img) {
|
||||
$src = $img->getAttribute('src');
|
||||
if (!$src) {
|
||||
$src = $img->getAttribute('data-src');
|
||||
if (!$src) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$img->outertext = '<img src="' . $src . '">';
|
||||
}
|
||||
|
||||
$categories = array();
|
||||
foreach($post->find('.tags__tag') as $tag) {
|
||||
if ($tag->getAttribute('data-tag')) {
|
||||
$categories[] = $tag->innertext;
|
||||
}
|
||||
}
|
||||
|
||||
$title = $post->find('.story__title-link', 0);
|
||||
|
||||
$item = array();
|
||||
$item['categories'] = $categories;
|
||||
$item['author'] = $post->find('.user__nick', 0)->innertext;
|
||||
$item['title'] = $title->plaintext;
|
||||
$item['content'] = strip_tags(backgroundToImg($post->find('.story__content-inner', 0)->innertext), '<br><p><img>');
|
||||
$item['uri'] = $title->href;
|
||||
$item['timestamp'] = strtotime($time->getAttribute('datetime'));
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -25,6 +25,10 @@ class PinterestBridge extends FeedExpander {
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://s.pinimg.com/webapp/style/images/favicon-9f8f9adf.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By username and board':
|
||||
@@ -44,7 +48,7 @@ class PinterestBridge extends FeedExpander {
|
||||
$pattern = '/https\:\/\/i\.pinimg\.com\/[a-zA-Z0-9]*x\//';
|
||||
foreach($this->items as $item) {
|
||||
|
||||
$item["content"] = preg_replace($pattern, 'https://i.pinimg.com/originals/', $item["content"]);
|
||||
$item['content'] = preg_replace($pattern, 'https://i.pinimg.com/originals/', $item['content']);
|
||||
$newitems[] = $item;
|
||||
}
|
||||
$this->items = $newitems;
|
||||
@@ -64,10 +68,10 @@ class PinterestBridge extends FeedExpander {
|
||||
// provide even less info. Thus we attempt multiple options.
|
||||
$item['title'] = trim($result['title']);
|
||||
|
||||
if($item['title'] === "")
|
||||
if($item['title'] === '')
|
||||
$item['title'] = trim($result['rich_summary']['display_name']);
|
||||
|
||||
if($item['title'] === "")
|
||||
if($item['title'] === '')
|
||||
$item['title'] = trim($result['grid_description']);
|
||||
|
||||
$item['timestamp'] = strtotime($result['created_at']);
|
||||
|
74
bridges/PixivBridge.php
Normal file
74
bridges/PixivBridge.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
class PixivBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'Pixiv Bridge';
|
||||
const URI = 'https://www.pixiv.net/';
|
||||
const DESCRIPTION = 'Returns the tag search from pixiv.net';
|
||||
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'tag' => array(
|
||||
'name' => 'Tag to search',
|
||||
'exampleValue' => 'example',
|
||||
'required' => true
|
||||
),
|
||||
));
|
||||
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getContents(static::URI . 'search.php?word=' . urlencode($this->getInput('tag')))
|
||||
or returnClientError('Unable to query pixiv.net');
|
||||
$regex = '/<input type="hidden"id="js-mount-point-search-result-list"data-items="([^"]*)/';
|
||||
$timeRegex = '/img\/([0-9]{4})\/([0-9]{2})\/([0-9]{2})\/([0-9]{2})\/([0-9]{2})\/([0-9]{2})\//';
|
||||
|
||||
preg_match_all($regex, $html, $matches, PREG_SET_ORDER, 0);
|
||||
if(!$matches) return;
|
||||
|
||||
$content = json_decode(html_entity_decode($matches[0][1]), true);
|
||||
$count = 0;
|
||||
foreach($content as $result) {
|
||||
if($count == 10) break;
|
||||
$count++;
|
||||
|
||||
$item = array();
|
||||
$item['id'] = $result['illustId'];
|
||||
$item['uri'] = 'https://www.pixiv.net/member_illust.php?mode=medium&illust_id=' . $result['illustId'];
|
||||
$item['title'] = $result['illustTitle'];
|
||||
$item['author'] = $result['userName'];
|
||||
|
||||
preg_match_all($timeRegex, $result['url'], $dt, PREG_SET_ORDER, 0);
|
||||
$elementDate = DateTime::createFromFormat('YmdHis',
|
||||
$dt[0][1] . $dt[0][2] . $dt[0][3] . $dt[0][4] . $dt[0][5] . $dt[0][6],
|
||||
new DateTimeZone('Asia/Tokyo'));
|
||||
$item['timestamp'] = $elementDate->getTimestamp();
|
||||
|
||||
$item['content'] = "<img src='" . $this->cacheImage($result['url'], $item['id']) . "' />";
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function cacheImage($url, $illustId) {
|
||||
|
||||
$url = str_replace('_master1200', '', $url);
|
||||
$url = str_replace('c/240x240/img-master/', 'img-original/', $url);
|
||||
$path = CACHE_DIR . '/pixiv_img';
|
||||
|
||||
if(!is_dir($path))
|
||||
mkdir($path, 0755, true);
|
||||
|
||||
if(!is_file($path . '/' . $illustId . '.jpeg')) {
|
||||
$headers = array('Referer: https://www.pixiv.net/member_illust.php?mode=medium&illust_id=' . $illustId);
|
||||
$illust = getContents($url, $headers);
|
||||
if(strpos($illust, '404 Not Found') !== false) {
|
||||
$illust = getContents(str_replace('jpg', 'png', $url), $headers);
|
||||
}
|
||||
file_put_contents($path . '/' . $illustId . '.jpeg', $illust);
|
||||
}
|
||||
|
||||
return 'cache/pixiv_img/' . $illustId . '.jpeg';
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -58,7 +58,7 @@ class RTBFBridge extends BridgeAbstract {
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('c'))) {
|
||||
return $this->getInput('c') .' - RTBF Bridge';
|
||||
return $this->getInput('c') . ' - RTBF Bridge';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
|
34
bridges/RadioMelodieBridge.php
Normal file
34
bridges/RadioMelodieBridge.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
class RadioMelodieBridge extends BridgeAbstract {
|
||||
const NAME = 'Radio Melodie Actu';
|
||||
const URI = 'https://www.radiomelodie.com/';
|
||||
const DESCRIPTION = 'Retourne les actualités publiées par Radio Melodie';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'img/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . 'actu')
|
||||
or returnServerError('Could not request Radio Melodie.');
|
||||
$list = $html->find('div[class=actuitem]');
|
||||
foreach($list as $element) {
|
||||
$item = array();
|
||||
|
||||
// Get picture URL
|
||||
$pictureHTML = $element->find('div[class=picture]');
|
||||
preg_match(
|
||||
'/background-image:url\((.*)\);/',
|
||||
$pictureHTML[0]->getAttribute('style'),
|
||||
$pictures);
|
||||
$pictureURL = $pictures[1];
|
||||
|
||||
$item['enclosures'] = array($pictureURL);
|
||||
$item['uri'] = self::URI . $element->parent()->href;
|
||||
$item['title'] = $element->find('h3', 0)->plaintext;
|
||||
$item['content'] = $element->find('p', 0)->plaintext . '<br/><img src="' . $pictureURL . '"/>';
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,10 +7,14 @@ class RainbowSixSiegeBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Latest articles from the Rainbow Six Siege blog';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://ubistatic19-a.akamaihd.net/resource/en-us/game/rainbow6/siege-v3/r6s-favicon_316592.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$dlUrl = "https://prod-tridionservice.ubisoft.com/live/v1/News/Latest?templateId=tcm%3A152-7677";
|
||||
$dlUrl .= "8-32&pageIndex=0&pageSize=10&language=en-US&detailPageId=tcm%3A152-194572-64";
|
||||
$dlUrl .= "&keywordList=175426&siteId=undefined&useSeoFriendlyUrl=true";
|
||||
$dlUrl = 'https://prod-tridionservice.ubisoft.com/live/v1/News/Latest?templateId=tcm%3A152-7677';
|
||||
$dlUrl .= '8-32&pageIndex=0&pageSize=10&language=en-US&detailPageId=tcm%3A152-194572-64';
|
||||
$dlUrl .= '&keywordList=175426&siteId=undefined&useSeoFriendlyUrl=true';
|
||||
$jsonString = getContents($dlUrl) or returnServerError('Error while downloading the website content');
|
||||
|
||||
$json = json_decode($jsonString, true);
|
||||
|
@@ -25,7 +25,7 @@ class ReadComicsBridge extends BridgeAbstract {
|
||||
return $timestamp;
|
||||
}
|
||||
|
||||
$keywordsList = explode(";", $this->getInput('q'));
|
||||
$keywordsList = explode(';', $this->getInput('q'));
|
||||
foreach($keywordsList as $keywords) {
|
||||
$html = $this->getSimpleHTMLDOM(self::URI . 'comic/' . rawurlencode($keywords))
|
||||
or $this->returnServerError('Could not request readcomics.tv.');
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user