mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-18 06:11:33 +02:00
Compare commits
48 Commits
2018-09-09
...
2018-10-15
Author | SHA1 | Date | |
---|---|---|---|
|
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 |
223
README.md
223
README.md
@@ -1,10 +1,10 @@
|
||||
rss-bridge
|
||||
===
|
||||
[](UNLICENSE) [](https://github.com/rss-bridge/rss-bridge/releases/latest) [](https://travis-ci.org/RSS-Bridge/rss-bridge) [](https://hub.docker.com/r/rssbridge/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)
|
||||
Supported sites/pages (examples)
|
||||
===
|
||||
|
||||
* `Bandcamp` : Returns last release from [bandcamp](https://bandcamp.com/) for a tag
|
||||
@@ -25,107 +25,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`)
|
||||
* `curl` extension enabled in PHP config (`php.ini`)
|
||||
RSS-Bridge requires PHP 5.6 or higher with following extensions enabled:
|
||||
|
||||
Enabling/Disabling bridges
|
||||
- [`openssl`](https://secure.php.net/manual/en/book.openssl.php)
|
||||
- [`libxml`](https://secure.php.net/manual/en/book.libxml.php)
|
||||
- [`mbstring`](https://secure.php.net/manual/en/book.mbstring.php)
|
||||
- [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php)
|
||||
- [`curl`](https://secure.php.net/manual/en/book.curl.php)
|
||||
- [`json`](https://secure.php.net/manual/en/book.json.php)
|
||||
|
||||
Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
|
||||
|
||||
Enable / Disable bridges
|
||||
===
|
||||
|
||||
By default, the script creates `whitelist.txt` and adds the main bridges (see above). `whitelist.txt` is ignored by git, you can edit it:
|
||||
* to enable extra bridges (one bridge per line)
|
||||
* to disable main bridges (remove the line)
|
||||
* to enable all bridges (just one wildcard `*` as file content)
|
||||
RSS-Bridge allows you to take full control over which bridges are displayed to the user. That way you can host your own RSS-Bridge service with your favorite collection of bridges!
|
||||
|
||||
New bridges are disabled by default, so make sure to check regularly what's new and whitelist what you want!
|
||||
Find more information on the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting)
|
||||
|
||||
**Notice**: By default RSS-Bridge will only show a small subset of bridges. Make sure to read up on [whitelisting](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting) to unlock the full potential of RSS-Bridge!
|
||||
|
||||
Deploy
|
||||
===
|
||||
|
||||
Thanks to the community, hosting your own instance of RSS-Bridge is as easy as clicking a button!
|
||||
|
||||
[](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
|
||||
===
|
||||
@@ -134,10 +215,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.
|
||||
|
@@ -28,6 +28,13 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
)
|
||||
)
|
||||
),
|
||||
'Collection (Français)' => array(
|
||||
'colfr' => array(
|
||||
'name' => 'Collection id',
|
||||
'required' => true,
|
||||
'title' => 'ex. RC-014095 pour https://www.arte.tv/fr/videos/RC-014095/blow-up/'
|
||||
)
|
||||
),
|
||||
'Catégorie (Allemand)' => array(
|
||||
'catde' => array(
|
||||
'type' => 'list',
|
||||
@@ -45,6 +52,13 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
'Sonstiges' => 'AUT'
|
||||
)
|
||||
)
|
||||
),
|
||||
'Collection (Allemand)' => array(
|
||||
'colde' => array(
|
||||
'name' => 'Collection id',
|
||||
'required' => true,
|
||||
'title' => 'ex. RC-014095 pour https://www.arte.tv/de/videos/RC-014095/blow-up/'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -54,15 +68,24 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
$category = $this->getInput('catfr');
|
||||
$lang = 'fr';
|
||||
break;
|
||||
case 'Collection (Français)':
|
||||
$lang = 'fr';
|
||||
$collectionId = $this->getInput('colfr');
|
||||
break;
|
||||
case 'Catégorie (Allemand)':
|
||||
$category = $this->getInput('catde');
|
||||
$lang = 'de';
|
||||
break;
|
||||
case 'Collection (Allemand)':
|
||||
$lang = 'de';
|
||||
$collectionId = $this->getInput('colde');
|
||||
break;
|
||||
}
|
||||
|
||||
$url = 'https://api.arte.tv/api/opa/v3/videos?sort=-lastModified&limit=10&language='
|
||||
. $lang
|
||||
. ($category != null ? '&category.code=' . $category : '');
|
||||
. ($category != null ? '&category.code=' . $category : '')
|
||||
. ($collectionId != null ? '&collections.collectionId=' . $collectionId : '');
|
||||
|
||||
$header = array(
|
||||
'Authorization: Bearer ' . self::API_TOKEN
|
||||
|
62
bridges/AutoJMBridge.php
Normal file
62
bridges/AutoJMBridge.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?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 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
?>
|
83
bridges/BundesbankBridge.php
Normal file
83
bridges/BundesbankBridge.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?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 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -46,23 +46,914 @@ class DealabsBridge extends PepperBridgeAbstract {
|
||||
'required' => 'true',
|
||||
'title' => 'Groupe dont il faut afficher les deals',
|
||||
'values' => array(
|
||||
'Abonnements internet' => 'abonnements-internet',
|
||||
'Accessoires & gadgets' => 'accessoires-gadgets',
|
||||
'Accessoires photo' => 'accessoires-photo',
|
||||
'Accessoires vélo' => 'accessoires-velo',
|
||||
'Acer' => 'acer',
|
||||
'Adaptateurs' => 'adaptateurs',
|
||||
'Adhérents Fnac' => 'adherents-fnac',
|
||||
'adidas' => 'adidas',
|
||||
'adidas Stan Smith' => 'adidas-stan-smith',
|
||||
'adidas Superstar' => 'adidas-superstar',
|
||||
'adidas ZX Flux' => 'adidas-zx-flux',
|
||||
'Adoucissant' => 'adoucissant',
|
||||
'Agendas' => 'agendas',
|
||||
'Age of Empires' => 'age-of-empires',
|
||||
'Alarmes' => 'alarmes',
|
||||
'Alimentation & boissons' => 'alimentation-boissons',
|
||||
'Alimentation PC' => 'alimentation-pc',
|
||||
'Amazon Echo' => 'amazon-echo',
|
||||
'Amazon Fire TV' => 'amazon-fire-tv',
|
||||
'Amazon Kindle' => 'amazon-kindle',
|
||||
'Amazon Prime' => 'amazon-prime',
|
||||
'AMD Ryzen' => 'amd-ryzen',
|
||||
'AMD Vega' => 'amd-vega',
|
||||
'amiibo' => 'amiibo',
|
||||
'Amplis' => 'amplis',
|
||||
'Ampoules' => 'ampoules',
|
||||
'Animaux' => 'animaux',
|
||||
'Anker' => 'anker',
|
||||
'Antivirus' => 'antivirus',
|
||||
'Antivols' => 'antivols',
|
||||
'Appareils de musculation' => 'appareils-de-musculation',
|
||||
'Appareils photo' => 'appareils-photo',
|
||||
'Apple AirPods' => 'apple-airpods',
|
||||
'Apple' => 'apple',
|
||||
'Apple iPad' => 'apple-ipad',
|
||||
'Apple iPad Mini' => 'apple-ipad-mini',
|
||||
'Apple iPad Pro' => 'apple-ipad-pro',
|
||||
'Apple iPhone 6' => 'apple-iphone-6',
|
||||
'Apple iPhone 7' => 'apple-iphone-7',
|
||||
'Apple iPhone 8' => 'apple-iphone-8',
|
||||
'Apple iPhone 8 Plus' => 'apple-iphone-8-plus',
|
||||
'Apple iPhone' => 'apple-iphone',
|
||||
'Apple iPhone SE' => 'apple-iphone-se',
|
||||
'Apple iPhone X' => 'apple-iphone-x',
|
||||
'Apple MacBook Air' => 'apple-macbook-air',
|
||||
'Apple MacBook Pro' => 'apple-macbook-pro',
|
||||
'Apple TV' => 'apple-tv',
|
||||
'Apple Watch' => 'apple-watch',
|
||||
'Applications Android' => 'applications-android',
|
||||
'Applications' => 'applications',
|
||||
'Applications iOS' => 'applications-ios',
|
||||
'Applis & logiciels' => 'applis-logiciels',
|
||||
'Arbres à chat' => 'arbres-a-chat',
|
||||
'Asmodée' => 'asmodee',
|
||||
'Aspirateurs' => 'aspirateurs',
|
||||
'Aspirateurs Dyson' => 'aspirateurs-dyson',
|
||||
'Aspirateurs robot' => 'aspirateurs-robot',
|
||||
'Assassin's Creed' => 'assassin-s-creed',
|
||||
'Assassin's Creed Origins' => 'assassin-s-creed-origins',
|
||||
'Assurances' => 'assurances',
|
||||
'Asus' => 'asus',
|
||||
'ASUS Transformer' => 'asus-transformer',
|
||||
'Asus ZenFone 2' => 'asus-zenfone-2',
|
||||
'Asus ZenFone 3' => 'asus-zenfone-3',
|
||||
'Asus ZenFone 4' => 'asus-zenfone-4',
|
||||
'Asus ZenFone GO' => 'asus-zenfone-go',
|
||||
'Aukey' => 'aukey',
|
||||
'Auto' => 'auto',
|
||||
'Auto-Moto' => 'auto-moto',
|
||||
'Autoradios' => 'autoradios',
|
||||
'Baby foot' => 'baby-foot',
|
||||
'BabyLiss' => 'babyliss',
|
||||
'Babyphones' => 'babyphones',
|
||||
'Bagagerie' => 'bagagerie',
|
||||
'Balançoires' => 'balancoires',
|
||||
'Bandes dessinées' => 'bandes-dessinees',
|
||||
'Banques' => 'banques',
|
||||
'Barbecue' => 'barbecue',
|
||||
'Barbie' => 'barbie',
|
||||
'Barres de son' => 'barres-de-son',
|
||||
'Batteries externes' => 'batteries-externes',
|
||||
'Battlefield 1' => 'battlefield-1',
|
||||
'Battlefield' => 'battlefield',
|
||||
'Béaba' => 'beaba',
|
||||
'Beats by Dre' => 'beats-by-dre',
|
||||
'BenQ' => 'benq',
|
||||
'Be quiet!' => 'be-quiet',
|
||||
'Biberons' => 'biberons',
|
||||
'Bières' => 'bieres',
|
||||
'Bijoux' => 'bijoux',
|
||||
'Billets d'avion' => 'billets-d-avion',
|
||||
'BioShock' => 'bioshock',
|
||||
'BioShock Infinite' => 'bioshock-infinite',
|
||||
'Bitdefender' => 'bitdefender',
|
||||
'Blackberry' => 'blackberry',
|
||||
'Black & Decker' => 'black-decker',
|
||||
'Blédina' => 'bledina',
|
||||
'Blu-Ray' => 'blu-ray',
|
||||
'Boissons' => 'boissons',
|
||||
'Boîtes à outils' => 'boites-a-outils',
|
||||
'Boîtiers PC' => 'boitiers-pc',
|
||||
'Bonbons' => 'bonbons',
|
||||
'Borderlands' => 'borderlands',
|
||||
'Bosch' => 'bosch',
|
||||
'Bose' => 'bose',
|
||||
'Bose SoundLink' => 'bose-soundlink',
|
||||
'Bottes' => 'bottes',
|
||||
'Box beauté' => 'box-beaute',
|
||||
'Bracelet fitness' => 'bracelet-fitness',
|
||||
'Brandt' => 'brandt',
|
||||
'Braun Silk Épil' => 'braun-silk-epil',
|
||||
'Bricolage' => 'bricolage',
|
||||
'Brosses à dents' => 'brosses-a-dents',
|
||||
'Cable management' => 'cable-management',
|
||||
'Câbles' => 'cables',
|
||||
'Câbles HDMI' => 'cables-hdmi',
|
||||
'Câbles USB' => 'cables-usb',
|
||||
'Cadres' => 'cadres',
|
||||
'Café' => 'cafe',
|
||||
'Café en grain' => 'cafe-en-grain',
|
||||
'Cafetières' => 'cafetieres',
|
||||
'Cahiers' => 'cahiers',
|
||||
'Call of Duty' => 'call-of-duty',
|
||||
'Call of Duty: Infinite Warfare' => 'call-of-duty-infinite-warfare',
|
||||
'Calor' => 'calor',
|
||||
'Caméras' => 'cameras',
|
||||
'Caméras IP' => 'cameras-ip',
|
||||
'Camping' => 'camping',
|
||||
'Carburant' => 'carburant',
|
||||
'Cartables' => 'cartables',
|
||||
'Cartes graphiques' => 'cartes-graphiques',
|
||||
'Cartes mères' => 'cartes-meres',
|
||||
'Cartes postales' => 'cartes-postales',
|
||||
'Casques audio' => 'casques-audio',
|
||||
'Casques sans fil' => 'casques-sans-fil',
|
||||
'Casquettes' => 'casquettes',
|
||||
'Casseroles' => 'casseroles',
|
||||
'CDAV' => 'cdav',
|
||||
'Ceintures' => 'ceintures',
|
||||
'Chaises' => 'chaises',
|
||||
'Chaises hautes' => 'chaises-hautes',
|
||||
'Chargeurs' => 'chargeurs',
|
||||
'Chasse' => 'chasse',
|
||||
'Chats' => 'chats',
|
||||
'Chaussons' => 'chaussons',
|
||||
'Chaussures adidas' => 'chaussures-adidas',
|
||||
'Chaussures' => 'chaussures',
|
||||
'Chaussures de football' => 'chaussures-de-football',
|
||||
'Chaussures de randonnée' => 'chaussures-de-randonnee',
|
||||
'Chaussures de running' => 'chaussures-de-running',
|
||||
'Chaussures de ski' => 'chaussures-de-ski',
|
||||
'Chaussures de ville' => 'chaussures-de-ville',
|
||||
'Chaussures Nike' => 'chaussures-nike',
|
||||
'Chelsea boots' => 'chelsea-boots',
|
||||
'Chemises' => 'chemises',
|
||||
'Chiens' => 'chiens',
|
||||
'Chocolat' => 'chocolat',
|
||||
'Chuck Taylor' => 'chuck-taylor',
|
||||
'Cinéma' => 'cinema',
|
||||
'Civilization' => 'civilization',
|
||||
'Civilization VI' => 'civilization-vi',
|
||||
'Clarks' => 'clarks',
|
||||
'Claviers' => 'claviers',
|
||||
'Claviers gamer' => 'claviers-gamer',
|
||||
'Claviers mécaniques' => 'claviers-mecaniques',
|
||||
'Clés USB' => 'cles-usb',
|
||||
'Composteurs' => 'composteurs',
|
||||
'Concerts' => 'concerts',
|
||||
'Congélateurs' => 'congelateurs',
|
||||
'Consoles' => 'consoles',
|
||||
'Consoles & jeux vidéo' => 'consoles-jeux-video',
|
||||
'Converse' => 'converse',
|
||||
'Costumes' => 'costumes',
|
||||
'Couches' => 'couches',
|
||||
'Couettes' => 'couettes',
|
||||
'Couteaux de cuisine' => 'couteaux-de-cuisine',
|
||||
'Couverts' => 'couverts',
|
||||
'Covoiturage' => 'covoiturage',
|
||||
'Crédits' => 'credits',
|
||||
'Croquettes pour chien' => 'croquettes-pour-chien',
|
||||
'Cuisinières' => 'cuisinieres',
|
||||
'Culture & divertissement' => 'culture-divertissement',
|
||||
'Cyclisme' => 'cyclisme',
|
||||
'DDR3' => 'ddr3',
|
||||
'DDR4' => 'ddr4',
|
||||
'Décoration' => 'decoration',
|
||||
'Deezer' => 'deezer',
|
||||
'Dell' => 'dell',
|
||||
'Delsey' => 'delsey',
|
||||
'Denon' => 'denon',
|
||||
'Dentifrices' => 'dentifrices',
|
||||
'Destiny 2' => 'destiny-2',
|
||||
'Destiny' => 'destiny',
|
||||
'Dishonored' => 'dishonored',
|
||||
'Disneyland Paris' => 'disneyland-paris',
|
||||
'Disques durs externes' => 'disques-durs-externes',
|
||||
'Disques durs internes' => 'disques-durs',
|
||||
'DJI' => 'dji',
|
||||
'Dosettes Nespresso' => 'dosettes-nespresso',
|
||||
'Dosettes Senseo' => 'dosettes-senseo',
|
||||
'Dosettes Tassimo' => 'dosettes-tassimo',
|
||||
'Draisiennes' => 'draisiennes',
|
||||
'Drones' => 'drones',
|
||||
'Durex' => 'durex',
|
||||
'DVD' => 'dvd',
|
||||
'Dyson' => 'dyson',
|
||||
'Eastpak' => 'eastpak',
|
||||
'ebooks' => 'ebooks',
|
||||
'Écharpes & foulards' => 'echarpes-et-foulards',
|
||||
'Écouteurs' => 'ecouteurs',
|
||||
'Écouteurs intra-auriculaires' => 'ecouteurs-intra-auriculaires',
|
||||
'Écouteurs sans fil' => 'ecouteurs-sans-fil',
|
||||
'Écouteurs sport' => 'ecouteurs-sport',
|
||||
'Écrans 21" et moins' => 'ecrans-21-pouces-et-moins',
|
||||
'Écrans 24"' => 'ecrans-24-pouces',
|
||||
'Écrans 27"' => 'ecrans-27-pouces',
|
||||
'Écrans 29" et plus' => 'ecrans-29-pouces-et-plus',
|
||||
'Écrans 4K / UHD' => 'ecrans-4k-uhd',
|
||||
'Écrans Acer' => 'ecrans-acer',
|
||||
'Écrans Asus' => 'ecrans-asus',
|
||||
'Écrans BenQ' => 'ecrans-benq',
|
||||
'Écrans Dell' => 'ecrans-dell',
|
||||
'Écrans de projection' => 'ecrans-de-projection',
|
||||
'Écrans' => 'ecrans',
|
||||
'Écrans FreeSync' => 'ecrans-freesync',
|
||||
'Écrans gamer' => 'ecrans-gamer',
|
||||
'Écrans incurvés' => 'ecrans-incurves',
|
||||
'Écrans Philips' => 'ecrans-philips',
|
||||
'Écrans Samsung' => 'ecrans-samsung',
|
||||
'Électricité (matériel)' => 'electricite',
|
||||
'Electrolux' => 'electrolux',
|
||||
'Électroménager' => 'electromenager',
|
||||
'Embauchoirs' => 'embauchoirs',
|
||||
'Enceintes Bluetooth' => 'enceintes-bluetooth',
|
||||
'Enceintes' => 'enceintes',
|
||||
'Engrais' => 'engrais',
|
||||
'Entretien du jardin' => 'entretien-du-jardin',
|
||||
'Épicerie' => 'epicerie',
|
||||
'Épilateurs à lumière pulsée' => 'epilateurs-a-lumiere-pulsee',
|
||||
'Épilateurs électriques' => 'epilateurs-electriques',
|
||||
'Épilation' => 'epilation',
|
||||
'Équipement auto' => 'equipement-auto',
|
||||
'Équipement motard' => 'equipement-motard',
|
||||
'Équipement sportif' => 'equipement-sportif',
|
||||
'Érotisme' => 'erotisme',
|
||||
'Escarpins' => 'escarpins',
|
||||
'Événements sportifs' => 'evenements-sportifs',
|
||||
'Expositions' => 'expositions',
|
||||
'F1 2017' => 'f1-2017',
|
||||
'Facom' => 'facom',
|
||||
'Fallout 4' => 'fallout-4',
|
||||
'Fallout' => 'fallout',
|
||||
'Fards à paupières' => 'fards-a-paupieres',
|
||||
'Fast-foods' => 'fast-foods',
|
||||
'Fauteuils' => 'fauteuils',
|
||||
'Fers à lisser / à friser' => 'fers-a-lisser-a-friser',
|
||||
'Fers à souder' => 'fers-a-souder',
|
||||
'Festivals' => 'festivals',
|
||||
'Feutres' => 'feutres',
|
||||
'FIFA 17' => 'fifa-17',
|
||||
'FIFA 18' => 'fifa-18',
|
||||
'FIFA 19' => 'fifa-19',
|
||||
'FIFA' => 'fifa',
|
||||
'Figurines' => 'figurines',
|
||||
'Films' => 'films',
|
||||
'Final Fantasy' => 'final-fantasy',
|
||||
'Final Fantasy XII' => 'final-fantasy-xii',
|
||||
'fitbit' => 'fitbit',
|
||||
'Flash' => 'flash',
|
||||
'Fluval' => 'fluval',
|
||||
'Foires & salons' => 'foires-et-salons',
|
||||
'Fonds de teint' => 'fonds-de-teint',
|
||||
'Football' => 'football',
|
||||
'Forfaits mobiles' => 'forfaits-mobiles',
|
||||
'For Honor' => 'for-honor',
|
||||
'Formule 1' => 'formule-1',
|
||||
'Fortnite' => 'fortnite',
|
||||
'Forza Horizon 3' => 'forza-horizon-3',
|
||||
'Forza Motorsport 7' => 'forza-motorsport-7',
|
||||
'Fossil' => 'fossil',
|
||||
'Fournitures de bureau' => 'fournitures-de-bureau',
|
||||
'Fournitures scolaires' => 'fournitures-scolaires',
|
||||
'Fours à poser' => 'fours-a-poser',
|
||||
'Fours encastrables' => 'fours-encastrables',
|
||||
'Fours' => 'fours',
|
||||
'Friandises pour chat' => 'friandises-pour-chat',
|
||||
'Friandises pour chien' => 'friandises-pour-chien',
|
||||
'Friskies' => 'friskies',
|
||||
'Fruits & légumes' => 'fruits-et-legumes',
|
||||
'FURminator' => 'furminator',
|
||||
'Futuroscope' => 'futuroscope',
|
||||
'Gamelles' => 'gamelles',
|
||||
'Game of Thrones' => 'game-of-thrones',
|
||||
'Gants' => 'gants',
|
||||
'Gants moto' => 'gants-moto',
|
||||
'Garmin' => 'garmin',
|
||||
'Gâteaux & biscuits' => 'gateaux-et-biscuits',
|
||||
'Gels douche' => 'gels-douche',
|
||||
'Geox' => 'geox',
|
||||
'Gigoteuses' => 'gigoteuses',
|
||||
'Gillette' => 'gillette',
|
||||
'Glaces' => 'glaces',
|
||||
'God of War' => 'god-of-war',
|
||||
'Google Chromecast' => 'google-chromecast',
|
||||
'Google Home' => 'google-home',
|
||||
'Google Pixel 2' => 'google-pixel-2',
|
||||
'Google Pixel 2 XL' => 'google-pixel-2-xl',
|
||||
'Google Pixel' => 'google-pixel',
|
||||
'Google Pixel XL' => 'google-pixel-xl',
|
||||
'GoPro Hero' => 'gopro-hero',
|
||||
'Gran Turismo' => 'gran-turismo',
|
||||
'Gratuit' => 'gratuit',
|
||||
'Grille-pain' => 'grille-pain',
|
||||
'GTA' => 'gta',
|
||||
'GTA V' => 'gta-v',
|
||||
'Guitares' => 'guitares',
|
||||
'Gyropodes' => 'gyropodes',
|
||||
'Haltères & poids' => 'halteres-et-poids',
|
||||
'Hamacs' => 'hamacs',
|
||||
'Hama' => 'hama',
|
||||
'Hand spinners' => 'hand-spinners',
|
||||
'Harnais pour chien' => 'harnais-pour-chien',
|
||||
'Harry Potter' => 'harry-potter',
|
||||
'Havaianas' => 'havaianas',
|
||||
'HDD' => 'hdd',
|
||||
'Hisense' => 'hisense',
|
||||
'Home Cinéma' => 'home-cinema',
|
||||
'Honor 6X' => 'honor-6x',
|
||||
'Honor 8' => 'honor-8',
|
||||
'Honor 8 Pro' => 'honor-8-pro',
|
||||
'Honor 9' => 'honor-9',
|
||||
'Horizon Zero Dawn' => 'horizon-zero-dawn',
|
||||
'Hôtels' => 'hotels',
|
||||
'Hoverboards' => 'hoverboards',
|
||||
'HTC 10' => 'htc-10',
|
||||
'HTC Desire' => 'htc-desire',
|
||||
'HTC One M9' => 'htc-one-m9',
|
||||
'HTC U11' => 'htc-u11',
|
||||
'HTC U Play' => 'htc-u-play',
|
||||
'HTC U Ultra' => 'htc-u-ultra',
|
||||
'HTC Vive' => 'htc-vive',
|
||||
'Huawei Mate 10' => 'huawei-mate-10',
|
||||
'Huawei Mate 9' => 'huawei-mate-9',
|
||||
'Huawei P10' => 'huawei-p10',
|
||||
'Huawei P10 Lite' => 'huawei-p10-lite',
|
||||
'Huawei P10 Plus' => 'huawei-p10-plus',
|
||||
'Huawei P20' => 'huawei-p20',
|
||||
'Huawei P20 Pro' => 'huawei-p20-pro',
|
||||
'Huawei P8 Lite' => 'huawei-p8-lite',
|
||||
'Huawei P9 Lite' => 'huawei-p9-lite',
|
||||
'Hubs' => 'hubs',
|
||||
'Huile moteur' => 'huile-moteur',
|
||||
'Hygiène corporelle' => 'hygiene-corporelle',
|
||||
'Hygiène de la maison' => 'hygiene-de-la-maison',
|
||||
'Hygiène des bébés' => 'hygiene-des-bebes',
|
||||
'Image, son & vidéo' => 'image-son-video',
|
||||
'Impressions photo' => 'impressions-photo',
|
||||
'Imprimantes 3D' => 'imprimantes-3d',
|
||||
'Imprimantes Brother' => 'imprimantes-brother',
|
||||
'Imprimantes Canon' => 'imprimantes-canon',
|
||||
'Imprimantes Epson' => 'imprimantes-epson',
|
||||
'Imprimantes HP' => 'imprimantes-hp',
|
||||
'Imprimantes' => 'imprimantes',
|
||||
'Imprimantes laser' => 'imprimantes-laser',
|
||||
'Imprimantes multifonctions' => 'imprimantes-multifonctions',
|
||||
'Informatique' => 'informatique',
|
||||
'Instruments de musique' => 'instruments-de-musique',
|
||||
'Intel i5' => 'intel-i5',
|
||||
'Intel i7' => 'intel-i7',
|
||||
'JBL Flip' => 'jbl-flip',
|
||||
'JBL' => 'jbl',
|
||||
'Jeans' => 'jeans',
|
||||
'Jeux d'apprentissage' => 'jeux-d-apprentissage',
|
||||
'Jeux d'extérieur' => 'jeux-d-exterieur',
|
||||
'Jeux d'imitation' => 'jeux-d-imitation',
|
||||
'Jeux de construction' => 'jeux-de-construction',
|
||||
'Jeux de société' => 'jeux-de-societe',
|
||||
'Jeux & jouets' => 'jeux-jouets',
|
||||
'Maison & jardin' => 'maison-jardin',
|
||||
'Jeux Nintendo Switch' => 'jeux-nintendo-switch',
|
||||
'Jeux & paris' => 'jeux-et-paris',
|
||||
'Jeux PC dématérialisés' => 'jeux-pc-dematerialises',
|
||||
'Jeux PlayStation 4' => 'jeux-playstation-4',
|
||||
'Jeux pour bébés' => 'jeux-pour-bebes',
|
||||
'Jeux PS4 dématérialisés' => 'jeux-ps4-dematerialises',
|
||||
'Jeux PS Plus' => 'jeux-ps-plus',
|
||||
'Jeux vidéo' => 'jeux-video',
|
||||
'Jeux Wii U' => 'jeux-wii-u',
|
||||
'Jeux Xbox dématérialisés' => 'jeux-xbox-dematerialises',
|
||||
'Jeux Xbox One' => 'jeux-xbox-one',
|
||||
'Jeux Xbox with Gold' => 'jeux-xbox-with-gold',
|
||||
'Journaux numériques' => 'journaux-numeriques',
|
||||
'Journaux papier' => 'journaux-papier',
|
||||
'Joy-Con' => 'manettes-nintendo-switch-joy-con',
|
||||
'Jungle Speed' => 'jungle-speed',
|
||||
'Kaspersky' => 'kaspersky',
|
||||
'Kinder' => 'kinder',
|
||||
'Kindle Paperwhite' => 'kindle-paperwhite',
|
||||
'Kindle Voyage' => 'kindle-voyage',
|
||||
'Kobo Aura 2' => 'kobo-aura-2',
|
||||
'Kobo Aura H2o' => 'kobo-aura-h2o',
|
||||
'Kobo' => 'kobo',
|
||||
'L'annale du destin' => 'l-annale-du-destin',
|
||||
'L'ombre de la guerre' => 'l-ombre-de-la-guerre',
|
||||
'L'ombre du Mordor' => 'l-ombre-du-mordor',
|
||||
'Lacoste' => 'lacoste',
|
||||
'Lapeyre' => 'lapeyre',
|
||||
'La Terre du Milieu' => 'la-terre-du-milieu',
|
||||
'Lavage auto' => 'lavage-auto',
|
||||
'Lave-linge frontal' => 'lave-linge-frontal',
|
||||
'Lave-linge' => 'lave-linge',
|
||||
'Lave-linge séchant' => 'lave-linge-sechant',
|
||||
'Lave-linge top' => 'lave-linge-top',
|
||||
'Lave-vaisselle' => 'lave-vaisselle',
|
||||
'Le bâton de la vérité' => 'le-baton-de-la-verite',
|
||||
'Lecteurs Blu-Ray' => 'lecteurs-blu-ray',
|
||||
'Lecteurs CD' => 'lecteurs-cd',
|
||||
'Lecteurs DVD' => 'lecteurs-dvd',
|
||||
'Lego' => 'lego',
|
||||
'Lego Star Wars' => 'lego-star-wars',
|
||||
'Lenovo K6 Note' => 'lenovo-k6-note',
|
||||
'Lenovo' => 'lenovo',
|
||||
'Lenovo P8' => 'lenovo-p8',
|
||||
'Lenovo Tab 3' => 'lenovo-tab-3',
|
||||
'Lenovo Tab 4' => 'lenovo-tab-4',
|
||||
'Lenovo Yoga' => 'lenovo-yoga',
|
||||
'Lenovo Yoga Tab 3' => 'lenovo-yoga-tab-3',
|
||||
'Lentilles de contact' => 'lentilles-de-contact',
|
||||
'Le Seigneur des anneaux' => 'le-seigneur-des-anneaux',
|
||||
'Les Sims' => 'les-sims',
|
||||
'Lessive' => 'lessive',
|
||||
'Levi's' => 'levi-s',
|
||||
'LG G4' => 'lg-g4',
|
||||
'LG G5' => 'lg-g5',
|
||||
'LG G6' => 'lg-g6',
|
||||
'LG' => 'lg',
|
||||
'LG OLED TV' => 'lg-oled-tv',
|
||||
'LG Q6' => 'lg-q6',
|
||||
'LG Q8' => 'lg-q8',
|
||||
'Life is Strange' => 'life-is-strange',
|
||||
'Linge de maison' => 'linge-de-maison',
|
||||
'Lingerie' => 'lingerie',
|
||||
'Lingettes pour bébés' => 'lingettes-pour-bebes',
|
||||
'Liseuses' => 'liseuses',
|
||||
'Litière pour chat' => 'litiere-pour-chat',
|
||||
'Lits' => 'lits',
|
||||
'Lits pour bébé' => 'lits-pour-bebe',
|
||||
'Livres audio' => 'livres-audio',
|
||||
'Livres' => 'livres',
|
||||
'Livres photo' => 'livres-photo',
|
||||
'Location de voiture' => 'location-de-voiture',
|
||||
'Logiciels de sécurité' => 'logiciels-de-securite',
|
||||
'Logiciels Microsoft' => 'logiciels-microsoft',
|
||||
'Logitech Harmony' => 'logitech-harmony',
|
||||
'Logitech' => 'logitech',
|
||||
'Loup-Garou' => 'loup-garou',
|
||||
'Lubrifiants' => 'lubrifiants',
|
||||
'Luminaires' => 'luminaires',
|
||||
'Lunettes de natation' => 'lunettes-de-natation',
|
||||
'Lunettes de soleil' => 'lunettes-de-soleil',
|
||||
'MacBook' => 'macbook',
|
||||
'Mac de bureau' => 'mac-de-bureau',
|
||||
'Machines à café à dosettes' => 'machines-a-cafe-a-dosettes',
|
||||
'Machines à café en grain' => 'machines-a-cafe-en-grain',
|
||||
'Machines à pain' => 'machines-a-pain',
|
||||
'Machines Dolce Gusto' => 'machines-dolce-gusto',
|
||||
'Machines Nespresso' => 'machines-nespresso',
|
||||
'Machines Senseo' => 'machines-senseo',
|
||||
'Magasins d'usine' => 'magasins-usine',
|
||||
'Magazines' => 'magazines',
|
||||
'Maillots de bain' => 'maillots-de-bain',
|
||||
'Maillots de football' => 'maillots-de-football',
|
||||
'Maison & Jardin' => 'maison-et-jardin',
|
||||
'Makita' => 'makita',
|
||||
'Manettes Nintendo Switch Pro' => 'manettes-nintendo-switch-pro',
|
||||
'Manettes PlayStation 4' => 'manettes-playstation-4',
|
||||
'Manettes Xbox One Elite' => 'manettes-xbox-one-elite',
|
||||
'Manettes Xbox One' => 'manettes-xbox-one',
|
||||
'Manix' => 'manix',
|
||||
'Manteaux' => 'manteaux',
|
||||
'Maquillage' => 'maquillage',
|
||||
'Mario Kart' => 'mario-kart',
|
||||
'Marteaux & maillets' => 'marteaux-et-maillets',
|
||||
'Mascara' => 'mascara',
|
||||
'Masques de ski' => 'masques-de-ski',
|
||||
'Mass Effect: Andromeda' => 'mass-effect-andromeda',
|
||||
'Matchs de football' => 'matchs-de-football',
|
||||
'Matelas gonflables' => 'matelas-gonflables',
|
||||
'Matelas' => 'matelas',
|
||||
'Matériaux de construction' => 'materiaux-de-construction',
|
||||
'Matériel de ski' => 'materiel-de-ski',
|
||||
'Medion' => 'medion',
|
||||
'Meubles pour chat' => 'meubles-pour-chat',
|
||||
'Micro-casques gaming' => 'micro-casques-gaming',
|
||||
'Micro-ondes' => 'micro-ondes',
|
||||
'Microphones' => 'microphones',
|
||||
'Micro-SD' => 'micro-sd',
|
||||
'Microsoft Office' => 'microsoft-office',
|
||||
'Microsoft Surface' => 'microsoft-surface',
|
||||
'Miele' => 'miele',
|
||||
'Minecraft' => 'minecraft',
|
||||
'Mixeurs' => 'mixeurs',
|
||||
'M&M's' => 'metm-s',
|
||||
'Mobilier' => 'mobilier',
|
||||
'Mode & accessoires' => 'mode-accessoires',
|
||||
'Santé & cosmétiques' => 'hygiene-sante-cosmetiques',
|
||||
'Mode enfants' => 'mode-enfants',
|
||||
'Mode femme' => 'mode-femme',
|
||||
'Mode homme' => 'mode-homme',
|
||||
'Modélisme' => 'modelisme',
|
||||
'Monopoly' => 'monopoly',
|
||||
'Montage PC' => 'montage-pc',
|
||||
'Montres' => 'montres',
|
||||
'Moto C Plus' => 'moto-c-plus',
|
||||
'Moto E4' => 'moto-e4',
|
||||
'Moto G5' => 'moto-g5',
|
||||
'Moto G5 Plus' => 'moto-g5-plus',
|
||||
'Moto G5S' => 'moto-g5s',
|
||||
'Moto G5S Plus' => 'moto-g5s-plus',
|
||||
'Moto M' => 'moto-m',
|
||||
'Moto' => 'moto',
|
||||
'Moto Z2' => 'moto-z2',
|
||||
'Moto Z2 Play' => 'moto-z2-play',
|
||||
'Moulinex' => 'moulinex',
|
||||
'Mousses à raser' => 'mousses-a-raser',
|
||||
'MSI' => 'msi',
|
||||
'Musées' => 'musees',
|
||||
'Musique' => 'musique',
|
||||
'NAS' => 'nas',
|
||||
'Natation' => 'natation',
|
||||
'Navigation' => 'navigation',
|
||||
'NERF' => 'nerf',
|
||||
'New Balance' => 'new-balance',
|
||||
'Nike Air Force' => 'nike-air-force',
|
||||
'Nike Air Max' => 'nike-air-max',
|
||||
'Nike Free' => 'nike-free',
|
||||
'Nike Huarache' => 'nike-huarache',
|
||||
'Nike' => 'nike',
|
||||
'Nintendo Classic Mini' => 'nintendo-classic-mini',
|
||||
'Nintendo' => 'nintendo',
|
||||
'Nintendo Switch' => 'nintendo-switch',
|
||||
'Nivea' => 'nivea',
|
||||
'Nokia 5' => 'nokia-5',
|
||||
'Nokia 6' => 'nokia-6',
|
||||
'Nokia 8' => 'nokia-8',
|
||||
'Nourriture pour chat' => 'nourriture-pour-chat',
|
||||
'Nourriture pour chien' => 'nourriture-pour-chien',
|
||||
'Nutella' => 'nutella',
|
||||
'Nvidia GeForce GTX 1060' => 'nvidia-geforce-gtx-1060',
|
||||
'Nvidia GeForce GTX 1070' => 'nvidia-geforce-gtx-1070',
|
||||
'Nvidia GeForce GTX 1080' => 'nvidia-geforce-gtx-1080',
|
||||
'Nvidia GeForce GTX 1080 Ti' => 'nvidia-geforce-gtx-1080-ti',
|
||||
'Nvidia' => 'nvidia',
|
||||
'Nvidia Shield' => 'nvidia-shield',
|
||||
'Objectifs' => 'objectifs',
|
||||
'Oculus Rift' => 'oculus-rift',
|
||||
'Oiseaux' => 'oiseaux',
|
||||
'OnePlus 5' => 'oneplus-5',
|
||||
'OnePlus 5T' => 'oneplus-5t',
|
||||
'OnePlus 6' => 'oneplus-6',
|
||||
'Onkyo' => 'onkyo',
|
||||
'Ordinateurs de bureau' => 'ordinateurs-de-bureau',
|
||||
'Oreillers' => 'oreillers',
|
||||
'Outillage' => 'outillage',
|
||||
'Outils de jardinage' => 'outils-de-jardinage',
|
||||
'Overwatch' => 'overwatch',
|
||||
'Packs clavier-souris' => 'packs-clavier-souris',
|
||||
'Paiement en ligne' => 'paiement-en-ligne',
|
||||
'Pampers' => 'pampers',
|
||||
'Panasonic' => 'panasonic',
|
||||
'Panier Plus' => 'panier-plus',
|
||||
'Pantalons' => 'pantalons',
|
||||
'Papeterie' => 'papeterie',
|
||||
'Papier peint' => 'papier-peint',
|
||||
'Papier toilette' => 'papier-toilette',
|
||||
'Parapharmacie' => 'parapharmacie',
|
||||
'Parc Astérix' => 'parc-asterix',
|
||||
'Parfums femme' => 'parfums-femme',
|
||||
'Parfums homme' => 'parfums-homme',
|
||||
'Parfums' => 'parfums',
|
||||
'Parkas' => 'parkas',
|
||||
'Parrot' => 'parrot',
|
||||
'Partitions' => 'partitions',
|
||||
'PC de bureau complets' => 'pc-de-bureau-complets',
|
||||
'PC gamer complets' => 'pc-gamer-complets',
|
||||
'PC hybrides' => 'hybrides',
|
||||
'PC portables' => 'pc-portables',
|
||||
'Pêche' => 'peche',
|
||||
'Peintures' => 'peintures',
|
||||
'Peluches' => 'peluches',
|
||||
'Perceuses' => 'perceuses',
|
||||
'Périphériques PC' => 'peripheriques-pc',
|
||||
'Pèse-personnes' => 'pese-personnes',
|
||||
'PES' => 'pro-evolution-soccer',
|
||||
'Petites voitures' => 'petites-voitures',
|
||||
'Philips Hue' => 'philips-hue',
|
||||
'Philips Lumea' => 'philips-lumea',
|
||||
'Philips One Blade' => 'philips-one-blade',
|
||||
'Philips' => 'philips',
|
||||
'Philips Sonicare' => 'philips-sonicare',
|
||||
'Photo' => 'photo',
|
||||
'Pièces auto' => 'pieces-auto',
|
||||
'Pièces moto' => 'pieces-moto',
|
||||
'Pièces vélo' => 'pieces-velo',
|
||||
'Piles' => 'piles',
|
||||
'Piles rechargeables' => 'piles-rechargeables',
|
||||
'Pinces' => 'pinces',
|
||||
'Pizza' => 'pizza',
|
||||
'Places de cinéma' => 'places-de-cinema',
|
||||
'Plage' => 'plage',
|
||||
'Plantes' => 'plantes',
|
||||
'Plaques de cuisson' => 'plaques-de-cuisson',
|
||||
'Platines vinyle' => 'platines-vinyle',
|
||||
'Playmobil' => 'playmobil',
|
||||
'PlayStation 4' => 'playstation-4',
|
||||
'PlayStation 4 Pro' => 'playstation-4-pro',
|
||||
'PlayStation 4 Slim' => 'playstation-4-slim',
|
||||
'PlayStation' => 'playstation',
|
||||
'PlayStation Plus' => 'playstation-plus',
|
||||
'Playstation Store' => 'playstation-store',
|
||||
'Plomberie' => 'plomberie',
|
||||
'Pneus' => 'pneus',
|
||||
'PocketBook' => 'pocketbook',
|
||||
'Poêles' => 'poeles',
|
||||
'Pokémon' => 'pokemon',
|
||||
'Portables gamer' => 'portables-gamer',
|
||||
'Porte-bébé' => 'porte-bebe',
|
||||
'Portefeuilles' => 'portefeuilles',
|
||||
'Posters' => 'posters',
|
||||
'Potager' => 'potager',
|
||||
'Poulaillers' => 'poulaillers',
|
||||
'Poupées' => 'poupees',
|
||||
'Poussettes' => 'poussettes',
|
||||
'Premiers secours' => 'premiers-secours',
|
||||
'Préservatifs' => 'preservatifs',
|
||||
'Princesse Tam-Tam' => 'princesse-tam-tam',
|
||||
'Processeurs' => 'processeurs',
|
||||
'Protection de la maison' => 'protection-de-la-maison',
|
||||
'Protections intimes' => 'protections-intimes',
|
||||
'Puériculture' => 'puericulture',
|
||||
'Pulls' => 'pulls',
|
||||
'Puma' => 'puma',
|
||||
'Purificateurs d'air' => 'purificateurs-d-air',
|
||||
'Purina' => 'purina',
|
||||
'Puzzles' => 'puzzles',
|
||||
'Pyjamas pour bébés' => 'pyjamas-pour-bebes',
|
||||
'Pyjamas' => 'pyjamas',
|
||||
'Qobuz' => 'qobuz',
|
||||
'RAM' => 'ram',
|
||||
'Randonnée' => 'randonnee',
|
||||
'Rasage' => 'rasage',
|
||||
'Rasoirs électriques' => 'rasoirs-electriques',
|
||||
'Rasoirs manuels' => 'rasoirs-manuels',
|
||||
'Raspberry Pi' => 'raspberry-pi',
|
||||
'Ray-Ban' => 'ray-ban',
|
||||
'Razer' => 'razer',
|
||||
'Réductions étudiants & jeunes' => 'reductions-etudiants-et-jeunes',
|
||||
'Reebok' => 'reebok',
|
||||
'Réfrigérateurs' => 'refrigerateurs',
|
||||
'Réhausseurs' => 'rehausseurs',
|
||||
'Remington' => 'remington',
|
||||
'Répéteurs' => 'repeteurs',
|
||||
'Réseau' => 'reseau',
|
||||
'Resident Evil 7' => 'resident-evil-7',
|
||||
'Resident Evil' => 'resident-evil',
|
||||
'Restaurants' => 'restaurants',
|
||||
'Richelieus' => 'richelieus',
|
||||
'Risk' => 'risk',
|
||||
'Rongeurs' => 'rongeurs',
|
||||
'Rouges à lèvres' => 'rouges-a-levres',
|
||||
'Routeurs' => 'routeurs',
|
||||
'Royal Canin' => 'royal-canin',
|
||||
'Running' => 'running',
|
||||
'Sacs à dos' => 'sacs-a-dos',
|
||||
'Sacs à langer' => 'sacs-a-langer',
|
||||
'Sacs à main' => 'sacs-a-main',
|
||||
'Samsonite' => 'samsonite',
|
||||
'Samsung Galaxy A5' => 'samsung-galaxy-a5',
|
||||
'Samsung Galaxy Note 8' => 'samsung-galaxy-note-8',
|
||||
'Samsung Galaxy S7 Edge' => 'samsung-galaxy-s7-edge',
|
||||
'Samsung Galaxy S7' => 'samsung-galaxy-s7',
|
||||
'Samsung Galaxy S8' => 'samsung-galaxy-s8',
|
||||
'Samsung Galaxy S8+' => 'samsung-galaxy-s8plus',
|
||||
'Samsung Galaxy S9' => 'samsung-galaxy-s9',
|
||||
'Samsung Galaxy Tab A' => 'samsung-galaxy-tab-a',
|
||||
'Samsung Galaxy Tab S2' => 'samsung-galaxy-tab-s2',
|
||||
'Samsung Galaxy Tab S3' => 'samsung-galaxy-tab-s3',
|
||||
'Samsung Gear' => 'samsung-gear',
|
||||
'Samsung Gear VR' => 'samsung-gear-vr',
|
||||
'Samsung' => 'samsung',
|
||||
'Sandales' => 'sandales',
|
||||
'SanDisk' => 'sandisk',
|
||||
'Santé & Cosmétiques' => 'sante-et-cosmetiques',
|
||||
'Savons' => 'savons',
|
||||
'Scanners' => 'scanners',
|
||||
'Scies' => 'scies',
|
||||
'Scooters' => 'scooters',
|
||||
'Seagate' => 'seagate',
|
||||
'Sécateurs' => 'secateurs',
|
||||
'Sèche-cheveux' => 'seche-cheveux',
|
||||
'Sèche-linge' => 'seche-linge',
|
||||
'Séjours' => 'sejours',
|
||||
'Sennheiser' => 'sennheiser',
|
||||
'Séries TV' => 'series-tv',
|
||||
'Services divers' => 'services-divers',
|
||||
'Serviettes hygiéniques' => 'serviettes-hygieniques',
|
||||
'Serviettes' => 'serviettes',
|
||||
'Sextoys' => 'sextoys',
|
||||
'Shorts de bain' => 'shorts-de-bain',
|
||||
'Shorts' => 'shorts',
|
||||
'Sièges auto' => 'sieges-auto',
|
||||
'Siemens' => 'siemens',
|
||||
'Skechers' => 'sketchers',
|
||||
'Ski' => 'ski',
|
||||
'Skyrim' => 'skyrim',
|
||||
'Smartbox' => 'smartbox',
|
||||
'Smart Home' => 'smart-home',
|
||||
'Smartphones à moins de 100€' => 'smartphones-moins-de-100',
|
||||
'Smartphones à moins de 200€' => 'smartphones-moins-de-200',
|
||||
'Smartphones Android' => 'smartphones-android',
|
||||
'Smartphones Huawei' => 'smartphones-huawei',
|
||||
'Smartphones Nokia' => 'smartphones-nokia',
|
||||
'Smartphones Samsung' => 'smartphones-samsung',
|
||||
'Smartphones' => 'smartphones',
|
||||
'Smartphones Xiaomi' => 'smartphones-xiaomi',
|
||||
'Smart TV' => 'smart-tv',
|
||||
'Smartwatch' => 'smartwatch',
|
||||
'Sneakers' => 'sneakers',
|
||||
'Soin des cheveux' => 'soin-des-cheveux',
|
||||
'Sonos PLAYBAR' => 'sonos-playbar',
|
||||
'Sonos' => 'sonos',
|
||||
'Sony PlayStation VR' => 'sony-playstation-vr',
|
||||
'Sony' => 'sony',
|
||||
'Sony Xperia XA1' => 'sony-xperia-xa1',
|
||||
'Sony Xperia X Compact' => 'sony-xperia-x-compact',
|
||||
'Sony Xperia XZ1 Compact' => 'sony-xperia-xz1-compact',
|
||||
'Sony Xperia XZ1' => 'sony-xperia-xz1',
|
||||
'Sony Xperia XZ Premium' => 'sony-xperia-xz-premium',
|
||||
'Sony Xperia Z3' => 'sony-xperia-z3',
|
||||
'Sorties' => 'sorties',
|
||||
'Souris gamer' => 'souris-gamer',
|
||||
'Souris Logitech' => 'souris-logitech',
|
||||
'Souris sans fil' => 'souris-sans-fil',
|
||||
'Souris' => 'souris',
|
||||
'South Park' => 'south-park',
|
||||
'Spectacles comiques' => 'spectacles-comiques',
|
||||
'Spectacles' => 'spectacles',
|
||||
'Sports & plein air' => 'sports-plein-air',
|
||||
'Spotify' => 'spotify',
|
||||
'SSD' => 'ssd',
|
||||
'Star Wars Battlefront' => 'star-wars-battlefront',
|
||||
'Stickers muraux' => 'stickers-muraux',
|
||||
'Stihl' => 'stihl',
|
||||
'Stockage externe' => 'stockage',
|
||||
'Streaming musical' => 'streaming-musical',
|
||||
'Stylos' => 'stylos',
|
||||
'Sucettes' => 'sucettes',
|
||||
'Super Mario' => 'super-mario',
|
||||
'Support GPS & smartphone' => 'support-gps-et-smartphone',
|
||||
'Surface Pro 4' => 'surface-pro-4',
|
||||
'Surgelés' => 'surgeles',
|
||||
'Surveillance' => 'surveillance',
|
||||
'Swatch' => 'swatch',
|
||||
'Switch réseau' => 'switch-reseau',
|
||||
'Systèmes d'exploitation' => 'systemes-d-exploitation',
|
||||
'Systèmes multiroom' => 'systemes-multiroom',
|
||||
'Tables à langer' => 'tables-a-langer',
|
||||
'Tables de camping' => 'tables-de-camping',
|
||||
'Tables de mixage' => 'tables-de-mixage',
|
||||
'Tables' => 'tables',
|
||||
'Tablettes graphiques Huion' => 'huion',
|
||||
'Tablettes graphiques' => 'tablettes-graphiques',
|
||||
'Tablettes graphiques Wacom' => 'wacom',
|
||||
'Tablettes Lenovo' => 'tablettes-lenovo',
|
||||
'Tablettes Samsung' => 'tablettes-samsung',
|
||||
'Tablettes' => 'tablettes',
|
||||
'Tablettes Xiaomi' => 'tablettes-xiaomi',
|
||||
'Tampons' => 'tampons',
|
||||
'Tapis' => 'tapis',
|
||||
'Taxis' => 'taxis',
|
||||
'Tefal' => 'tefal',
|
||||
'Télécommandes' => 'telecommandes',
|
||||
'Téléphones fixes' => 'telephones-fixes',
|
||||
'Téléphonie' => 'telephonie',
|
||||
'Voyages & sorties' => 'voyages-sorties-restaurants',
|
||||
'Téléviseurs' => 'televiseurs',
|
||||
'Tentes' => 'tentes',
|
||||
'Têtes de brosse à dents de rechange' => 'tetes-de-brosse-a-dents-de-rechange',
|
||||
'Théâtre' => 'theatre',
|
||||
'The Legend of Zelda' => 'the-legend-of-zelda',
|
||||
'Thermomètres' => 'thermometres',
|
||||
'Thermomix' => 'thermomix',
|
||||
'Thés glacés' => 'thes-glaces',
|
||||
'Thés' => 'thes',
|
||||
'The Walking dead' => 'the-walking-dead',
|
||||
'The Witcher 3' => 'the-witcher-3',
|
||||
'The Witcher' => 'the-witcher',
|
||||
'Time's Up!' => 'time-s-up',
|
||||
'Tom Clancy's Ghost Recon: Wildlands' => 'tom-clancy-s-ghost-recon-wildlands',
|
||||
'Tom Clancy's The Division' => 'tom-clancy-s-the-division',
|
||||
'Tom Clancy's' => 'tom-clancy-s',
|
||||
'TomTom' => 'tomtom',
|
||||
'Tondeuses à gazon' => 'tondeuses-a-gazon',
|
||||
'Tondeuses' => 'tondeuses',
|
||||
'Toner' => 'toner',
|
||||
'Torchons' => 'torchons',
|
||||
'Toshiba' => 'toshiba',
|
||||
'Total War' => 'total-war',
|
||||
'Total War: Warhammer II' => 'total-war-warhammer-ii',
|
||||
'Total War: Warhammer' => 'total-war-warhammer',
|
||||
'Tournevis & visseuses' => 'tournevis-et-visseuses',
|
||||
'TP-Link' => 'tp-link',
|
||||
'Transats & cosys' => 'transats-et-cosys',
|
||||
'Transports en commun' => 'transports-en-commun',
|
||||
'Trixie' => 'trixie',
|
||||
'Tronçonneuses' => 'tronconneuses',
|
||||
'Trottinettes électriques' => 'trottinettes-electriques',
|
||||
'Trottinettes' => 'trottinettes',
|
||||
'T-shirts' => 't-shirts',
|
||||
'TV 39'' et moins' => 'tv-39-pouces-et-moins',
|
||||
'TV 40'' à 64''' => 'tv-40-pouces-a-64-pouces',
|
||||
'TV 4K' => 'tv-4k',
|
||||
'TV 65'' et plus' => 'tv-65-pouces-et-plus',
|
||||
'TV Full HD' => 'tv-full-hd',
|
||||
'TV incurvées' => 'tv-incurvees',
|
||||
'TV LG' => 'tv-lg',
|
||||
'TV OLED' => 'tv-oled',
|
||||
'TV Panasonic' => 'tv-panasonic',
|
||||
'TV Philips' => 'tv-philips',
|
||||
'TV Samsung' => 'tv-samsung',
|
||||
'TV Sony' => 'tv-sony',
|
||||
'Ultraportables' => 'ultraportables',
|
||||
'Uncharted 4' => 'uncharted-4',
|
||||
'Uncharted: The Lost Legacy' => 'uncharted-the-lost-legacy',
|
||||
'Uncharted' => 'uncharted',
|
||||
'Ustensiles de cuisine' => 'ustensiles-de-cuisine',
|
||||
'Ustensiles de cuisson' => 'ustensiles-de-cuisson',
|
||||
'Vaisselle' => 'vaisselle',
|
||||
'Valises cabine' => 'valises-cabine',
|
||||
'Valises rigides' => 'valises-rigides',
|
||||
'Valises' => 'valises',
|
||||
'Variétés & revues' => 'varietes-et-revues',
|
||||
'Vases' => 'vases',
|
||||
'Veet' => 'veet',
|
||||
'Vélos d'appartement' => 'velos-d-appartement',
|
||||
'Vélos' => 'velos',
|
||||
'Ventilateurs' => 'ventilateurs',
|
||||
'Ventirad' => 'ventirad',
|
||||
'Vernis à ongles' => 'vernis-a-ongles',
|
||||
'Vestes' => 'vestes',
|
||||
'Vêtements d'été' => 'vetements-d-ete',
|
||||
'Vêtements d'hiver' => 'vetements-d-hiver',
|
||||
'Vêtements de grossesse' => 'vetements-de-grossesse',
|
||||
'Vêtements de ski' => 'vetements-de-ski',
|
||||
'Vêtements de sport' => 'vetements-de-sport',
|
||||
'Vêtements pour bébé' => 'vetements-pour-bebe',
|
||||
'Vêtements techniques' => 'vetements-techniques',
|
||||
'Vidéoprojecteurs 3D' => 'videoprojecteurs-3d',
|
||||
'Vidéoprojecteurs Acer' => 'videoprojecteurs-acer',
|
||||
'Vidéoprojecteurs BenQ' => 'videoprojecteurs-benq',
|
||||
'Vidéoprojecteurs Epson' => 'videoprojecteurs-epson',
|
||||
'Vidéoprojecteurs HD' => 'videoprojecteurs-hd',
|
||||
'Vidéoprojecteurs LG' => 'videoprojecteurs-lg',
|
||||
'Vidéoprojecteurs Optoma' => 'videoprojecteurs-optoma',
|
||||
'Vidéoprojecteurs' => 'projecteurs',
|
||||
'Vidéo' => 'video',
|
||||
'Vins' => 'vins',
|
||||
'Visites & patrimoine' => 'visites-et-patrimoine',
|
||||
'VOD' => 'vod',
|
||||
'Voitures télécommandées' => 'voitures-telecommandees',
|
||||
'Voyages & sorties' => 'voyages-et-sorties',
|
||||
'Voyages' => 'voyages',
|
||||
'VPN' => 'vpn',
|
||||
'VR' => 'vr',
|
||||
'VTC' => 'vtc',
|
||||
'VTT' => 'vtt',
|
||||
'Wacom Cintiq' => 'cintiq',
|
||||
'Watercooling' => 'watercooling',
|
||||
'WD (Western Digital)' => 'western-digital',
|
||||
'Wearables' => 'wearables',
|
||||
'Whey' => 'whey',
|
||||
'Whirlpool' => 'whirlpool',
|
||||
'Whiskas' => 'whiskas',
|
||||
'Wii U' => 'wii-u',
|
||||
'Wiko' => 'wiko',
|
||||
'Windows' => 'windows',
|
||||
'WindScribe' => 'windscribe',
|
||||
'Wolfenstein II: The New Colossus' => 'wolfenstein-ii-the-new-colossus',
|
||||
'Wolfenstein' => 'wolfenstein',
|
||||
'Wonderbox' => 'wonderbox',
|
||||
'Xbox Live' => 'xbox-live',
|
||||
'Xbox One S' => 'xbox-one-s',
|
||||
'Xbox One' => 'xbox-one',
|
||||
'Xbox One X' => 'xbox-one-x',
|
||||
'Xbox' => 'xbox',
|
||||
'Xiaomi Mi6' => 'xiaomi-mi6',
|
||||
'Xiaomi Mi A1' => 'xiaomi-mi-a1',
|
||||
'Xiaomi Mi Band' => 'xiaomi-mi-band',
|
||||
'Xiaomi Mi Box' => 'xiaomi-mi-box',
|
||||
'Xiaomi Mi Max' => 'xiaomi-mi-max',
|
||||
'Xiaomi Mi Mix 2' => 'xiaomi-mi-mix-2',
|
||||
'Xiaomi Mi Mix' => 'xiaomi-mi-mix',
|
||||
'Xiaomi Mi Pad 3' => 'xiaomi-mi-pad-3',
|
||||
'Xiaomi Redmi 4A' => 'xiaomi-redmi-4a',
|
||||
'Xiaomi Redmi 4X' => 'xiaomi-redmi-4x',
|
||||
'Xiaomi Redmi Note 4' => 'xiaomi-redmi-note-4',
|
||||
'Xiaomi Smart Home' => 'xiaomi-smart-home',
|
||||
'Xiaomi' => 'xiaomi',
|
||||
'Yamaha' => 'yamaha',
|
||||
'Zelda: Breath of the Wild' => 'zelda-breath-of-the-wild',
|
||||
'Zoos' => 'zoos',
|
||||
)
|
||||
),
|
||||
'order' => array(
|
||||
@@ -229,10 +1120,8 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
$selectorHot = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'flex',
|
||||
'flex--align-c',
|
||||
'flex--justify-space-between',
|
||||
'space--b-2',
|
||||
'cept-vote-box',
|
||||
'vote-box'
|
||||
)
|
||||
);
|
||||
|
||||
@@ -251,8 +1140,7 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
array(
|
||||
'size--all-s',
|
||||
'flex',
|
||||
'flex--justify-e',
|
||||
'flex--grow-1',
|
||||
'boxAlign-jc--all-fe'
|
||||
)
|
||||
);
|
||||
|
||||
@@ -284,7 +1172,8 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
. $this->GetSource($deal)
|
||||
. $deal->find('div[class*='. $selectorDescription .']', 0)->innertext
|
||||
. '</td><td>'
|
||||
. $deal->find('div[class='. $selectorHot .']', 0)->children(0)->outertext
|
||||
. $deal->find('div[class*='. $selectorHot .']', 0)
|
||||
->find('span', 1)->outertext
|
||||
. '</td></table>';
|
||||
$dealDateDiv = $deal->find('div[class*='. $selectorDate .']', 0)
|
||||
->find('span[class=hide--toW3]');
|
||||
|
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);
|
||||
}
|
||||
|
||||
}
|
@@ -94,17 +94,20 @@ class ETTVBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
protected $results_link;
|
||||
|
||||
public function collectData(){
|
||||
// No control on inputs, because all have defaultValue set
|
||||
// No control on inputs, because all defaultValue are set
|
||||
$query_str = 'torrents-search.php';
|
||||
$query_str .= '?search=' . urlencode('+'.str_replace(' ', ' +', $this->getInput('query')));
|
||||
$query_str .= '&cat=' . $this->getInput('cat');
|
||||
$query_str .= 'incldead&=' . $this->getInput('status');
|
||||
$query_str .= '&incldead=' . $this->getInput('status');
|
||||
$query_str .= '&lang=' . $this->getInput('lang');
|
||||
$query_str .= '&sort=id&order=desc';
|
||||
|
||||
// Get results page
|
||||
$html = getSimpleHTMLDOM(self::URI . $query_str)
|
||||
$this->results_link = self::URI . $query_str;
|
||||
$html = getSimpleHTMLDOM($this->results_link)
|
||||
or returnServerError('Could not request ' . $this->getName());
|
||||
|
||||
// Loop on each entry
|
||||
@@ -125,7 +128,7 @@ class ETTVBridge extends BridgeAbstract {
|
||||
$item = array();
|
||||
$item['author'] = $details->children(6)->children(1)->plaintext;
|
||||
$item['title'] = $entry->title;
|
||||
$item['uri'] = $dllinks->children(0)->children(0)->children(0)->href;
|
||||
$item['uri'] = $link;
|
||||
$item['timestamp'] = strtotime($details->children(7)->children(1)->plaintext);
|
||||
$item['content'] = '';
|
||||
$item['content'] .= '<br/><b>Name: </b>' . $details->children(0)->children(1)->innertext;
|
||||
@@ -139,4 +142,20 @@ class ETTVBridge extends BridgeAbstract {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if($this->getInput('query')) {
|
||||
return '[' . self::NAME . '] ' . $this->getInput('query');
|
||||
}
|
||||
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(isset($this->results_link) && !empty($this->results_link)) {
|
||||
return $this->results_link;
|
||||
}
|
||||
|
||||
return self::URI;
|
||||
}
|
||||
}
|
||||
|
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;
|
||||
}
|
||||
|
||||
}
|
@@ -17,22 +17,12 @@ class FB2Bridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
|
||||
function extractFromDelimiters($string, $start, $end){
|
||||
if(strpos($string, $start) !== false) {
|
||||
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
|
||||
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
|
||||
return $section_retrieved;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//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 . '"';
|
||||
@@ -119,7 +109,7 @@ EOD;
|
||||
}
|
||||
|
||||
//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>');
|
||||
|
||||
//Adapt link hrefs: convert relative links into absolute links and bypass external link redirection
|
||||
$content = preg_replace_callback('/ href=\"([^"]+)\"/i', $unescape_fb_link, $content);
|
||||
|
@@ -2,7 +2,7 @@
|
||||
class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene, logmanoriginal';
|
||||
const NAME = 'Facebook';
|
||||
const NAME = 'Facebook Bridge';
|
||||
const URI = 'https://www.facebook.com/';
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
|
||||
@@ -41,23 +41,71 @@ class FacebookBridge extends BridgeAbstract {
|
||||
'exampleValue' => 'https://www.facebook.com/groups/743149642484225',
|
||||
'title' => 'Insert group name or facebook group URL'
|
||||
)
|
||||
),
|
||||
'global' => array(
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Specify the number of items to return (default: -1)',
|
||||
'defaultValue' => -1
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private $authorName = '';
|
||||
private $groupName = '';
|
||||
|
||||
public function getName(){
|
||||
|
||||
switch($this->queriedContext) {
|
||||
|
||||
case 'User':
|
||||
if(!empty($this->authorName)) {
|
||||
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName
|
||||
. ' - ' . static::NAME;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Group':
|
||||
if(!empty($this->groupName)) {
|
||||
return $this->groupName . ' - ' . static::NAME;
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = self::URI;
|
||||
|
||||
switch($this->queriedContext) {
|
||||
|
||||
case 'Group':
|
||||
// Discover groups via https://www.facebook.com/groups/
|
||||
// Example group: https://www.facebook.com/groups/sailors.worldwide
|
||||
$uri .= 'groups/' . $this->sanitizeGroup(filter_var($this->getInput('g'), FILTER_SANITIZE_URL));
|
||||
break;
|
||||
|
||||
case 'User':
|
||||
// Example user 1: https://www.facebook.com/artetv/
|
||||
// Example user 2: artetv
|
||||
$user = $this->sanitizeUser($this->getInput('u'));
|
||||
|
||||
if(!strpos($user, '/')) {
|
||||
$uri .= '/pg/' . urlencode($user) . '/posts';
|
||||
} else {
|
||||
$uri .= 'pages/' . $user;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// Request the mobile version to reduce page size (no javascript)
|
||||
// More information: https://stackoverflow.com/a/11103592
|
||||
return $uri .= '?_fb_noscript=1';
|
||||
}
|
||||
|
||||
@@ -78,6 +126,12 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
}
|
||||
|
||||
$limit = $this->getInput('limit') ?: -1;
|
||||
|
||||
if($limit > 0 && count($this->items) > $limit) {
|
||||
$this->items = array_slice($this->items, 0, $limit);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region Group
|
||||
@@ -249,182 +303,223 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion (Group)
|
||||
|
||||
private function collectUserData(){
|
||||
#region User
|
||||
|
||||
//Utility function for cleaning a Facebook link
|
||||
$unescape_fb_link = function($matches){
|
||||
/**
|
||||
* 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']);
|
||||
|
||||
$header = array("Content-type:
|
||||
application/x-www-form-urlencoded\r\nReferer: $captcha_action\r\nCookie: noscript=1\r\n");
|
||||
$header = array(
|
||||
'Content-type: application/x-www-form-urlencoded',
|
||||
'Referer: ' . $captcha_action,
|
||||
'Cookie: noscript=1'
|
||||
);
|
||||
|
||||
$opts = array(
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_POSTFIELDS => http_build_query($captcha_fields)
|
||||
);
|
||||
|
||||
$html = getContents($captcha_action, $header, $opts);
|
||||
$html = getSimpleHTMLDOM($captcha_action, $header, $opts)
|
||||
or returnServerError('Failed to submit captcha response back to Facebook');
|
||||
|
||||
if($html === false) {
|
||||
returnServerError('Failed to submit captcha response back to Facebook');
|
||||
}
|
||||
unset($_SESSION['captcha_fields']);
|
||||
$html = str_get_html($html);
|
||||
return $html;
|
||||
}
|
||||
|
||||
unset($_SESSION['captcha_fields']);
|
||||
unset($_SESSION['captcha_action']);
|
||||
}
|
||||
|
||||
//Retrieve page contents
|
||||
return null;
|
||||
}
|
||||
|
||||
private function collectUserData(){
|
||||
|
||||
$html = $this->handleCaptchaResponse();
|
||||
|
||||
// Retrieve page contents
|
||||
if(is_null($html)) {
|
||||
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n");
|
||||
|
||||
// Check if the user provided a fully qualified URL
|
||||
if (filter_var($this->getInput('u'), FILTER_VALIDATE_URL)) {
|
||||
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE'));
|
||||
|
||||
$urlparts = parse_url($this->getInput('u'));
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header)
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
if($urlparts['host'] !== parse_url(self::URI)['host']) {
|
||||
returnClientError('The host you provided is invalid! Received "'
|
||||
. $urlparts['host']
|
||||
. '", expected "'
|
||||
. parse_url(self::URI)['host']
|
||||
. '"!');
|
||||
}
|
||||
|
||||
if(!array_key_exists('path', $urlparts)
|
||||
|| $urlparts['path'] === '/') {
|
||||
returnClientError('The URL you provided doesn\'t contain the user name!');
|
||||
}
|
||||
|
||||
$user = explode('/', $urlparts['path'])[1];
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . urlencode($user) . '?_fb_noscript=1', $header)
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
} else {
|
||||
|
||||
// First character cannot be a forward slash
|
||||
if(strpos($this->getInput('u'), '/') === 0) {
|
||||
returnClientError('Remove leading slash "/" from the username!');
|
||||
}
|
||||
|
||||
if(!strpos($this->getInput('u'), '/')) {
|
||||
$html = getSimpleHTMLDOM(self::URI . urlencode($this->getInput('u')) . '?_fb_noscript=1', $header)
|
||||
or returnServerError('No results for this query.');
|
||||
} else {
|
||||
$html = getSimpleHTMLDOM(self::URI . 'pages/' . $this->getInput('u') . '?_fb_noscript=1', $header)
|
||||
or returnServerError('No results for this query.');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Handle captcha form?
|
||||
// Handle captcha form?
|
||||
$captcha = $html->find('div.captcha_interstitial', 0);
|
||||
if (!is_null($captcha)) {
|
||||
//Save form for submitting after getting captcha response
|
||||
if (session_status() == PHP_SESSION_NONE)
|
||||
session_start();
|
||||
$captcha_fields = array();
|
||||
foreach ($captcha->find('input, button') as $input)
|
||||
$captcha_fields[$input->name] = $input->value;
|
||||
$_SESSION['captcha_fields'] = $captcha_fields;
|
||||
$_SESSION['captcha_action'] = $captcha->find('form', 0)->action;
|
||||
|
||||
//Show captcha filling form to the viewer, proxying the captcha image
|
||||
$img = base64_encode(getContents($captcha->find('img', 0)->src));
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/html');
|
||||
$message = <<<EOD
|
||||
<form method="post" action="?{$_SERVER['QUERY_STRING']}">
|
||||
<h2>Facebook captcha challenge</h2>
|
||||
<p>Unfortunately, rss-bridge cannot fetch the requested page.<br />
|
||||
Facebook wants rss-bridge to resolve the following captcha:</p>
|
||||
<p><img src="data:image/png;base64,{$img}" /></p>
|
||||
<p><b>Response:</b> <input name="captcha_response" placeholder="please fill in" />
|
||||
<input type="submit" value="Submit!" /></p>
|
||||
</form>
|
||||
EOD;
|
||||
die($message);
|
||||
if (!is_null($captcha)) {
|
||||
$this->returnCaptchaMessage($captcha);
|
||||
}
|
||||
|
||||
//No captcha? We can carry on retrieving page contents :)
|
||||
//First, we check wether the page is public or not
|
||||
// No captcha? We can carry on retrieving page contents :)
|
||||
// First, we check wether the page is public or not
|
||||
$loginForm = $html->find('._585r', 0);
|
||||
|
||||
if($loginForm != null) {
|
||||
returnServerError('You must be logged in to view this page. This is not supported by RSS-Bridge.');
|
||||
}
|
||||
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$element = $html
|
||||
->find('#pagelet_timeline_main_column')[0]
|
||||
->children(0)
|
||||
->children(0)
|
||||
->children(0)
|
||||
->next_sibling()
|
||||
->children(0);
|
||||
|
||||
if(isset($element)) {
|
||||
|
||||
defaultLinkTo($element, self::URI);
|
||||
|
||||
$author = str_replace(' | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
|
||||
$profilePic = 'https://graph.facebook.com/'
|
||||
. $this->getInput('u')
|
||||
. '/picture?width=200&height=200#.image';
|
||||
|
||||
$profilePic = $html->find('meta[property="og:image"]', 0)->content;
|
||||
|
||||
$this->authorName = $author;
|
||||
|
||||
@@ -480,19 +575,18 @@ EOD;
|
||||
'',
|
||||
$content);
|
||||
|
||||
//Remove "SpSonsSoriSsés"
|
||||
// Remove "SpSonsSoriSsés"
|
||||
$content = preg_replace(
|
||||
'/(?iU)<a [^>]+ href="#" role="link" [^>}]+>.+<\/a>/iU',
|
||||
'',
|
||||
$content);
|
||||
|
||||
//Remove html nodes, keep only img, links, basic formatting
|
||||
// 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',
|
||||
@@ -505,31 +599,31 @@ EOD;
|
||||
'aria-[^=]*',
|
||||
'role',
|
||||
'rel',
|
||||
'id') as $property_name)
|
||||
$content = preg_replace('/ ' . $property_name . '=\"[^"]*\"/i', '', $content);
|
||||
'id') as $property_name) {
|
||||
$content = preg_replace('/ ' . $property_name . '=\"[^"]*\"/i', '', $content);
|
||||
}
|
||||
|
||||
$content = preg_replace('/<\/a [^>]+>/i', '</a>', $content);
|
||||
|
||||
//Convert textual representation of emoticons eg
|
||||
//"<i><u>smile emoticon</u></i>" back to ASCII emoticons eg ":)"
|
||||
$content = preg_replace_callback(
|
||||
'/<i><u>([^ <>]+) ([^<>]+)<\/u><\/i>/i',
|
||||
$unescape_fb_emote,
|
||||
$content
|
||||
);
|
||||
$this->unescape_fb_emote($content);
|
||||
|
||||
//Retrieve date of the post
|
||||
// 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")) . '...';
|
||||
|
||||
@@ -545,8 +639,10 @@ EOD;
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['timestamp'] = $date;
|
||||
if(strpos($item['content'], '<img') === false)
|
||||
|
||||
if(strpos($item['content'], '<img') === false) {
|
||||
$item['enclosures'] = array($profilePic);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
@@ -555,25 +651,6 @@ EOD;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
#endregion (User)
|
||||
|
||||
switch($this->queriedContext) {
|
||||
|
||||
case 'User':
|
||||
if(!empty($this->authorName)) {
|
||||
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName
|
||||
. ' - Facebook Bridge';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Group':
|
||||
if(!empty($this->groupName)) {
|
||||
return $this->groupName . ' - Facebook Bridge';
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
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;
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
@@ -64,7 +64,7 @@ class KununuBridge extends BridgeAbstract {
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
function getName(){
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('company'))) {
|
||||
$company = $this->fixCompanyName($this->getInput('company'));
|
||||
return ($this->companyName ?: $company) . ' - ' . self::NAME;
|
||||
@@ -73,52 +73,67 @@ class KununuBridge extends BridgeAbstract {
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.kununu.com/favicon-196x196.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$full = $this->getInput('full');
|
||||
|
||||
// Load page
|
||||
$html = getSimpleHTMLDOMCached($this->getURI());
|
||||
if(!$html)
|
||||
returnServerError('Unable to receive data from ' . $this->getURI() . '!');
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Unable to receive data from ' . $this->getURI() . '!');
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
// Update name for this request
|
||||
$this->companyName = $this->extractCompanyName($html);
|
||||
$company = $html->find('span[class="company-name"]', 0)
|
||||
or returnServerError('Cannot find company name!');
|
||||
|
||||
$this->companyName = $company->innertext;
|
||||
|
||||
// Find the section with all the panels (reviews)
|
||||
$section = $html->find('section.kununu-scroll-element', 0);
|
||||
if($section === false)
|
||||
returnServerError('Unable to find panel section!');
|
||||
$section = $html->find('section.kununu-scroll-element', 0)
|
||||
or returnServerError('Unable to find panel section!');
|
||||
|
||||
// Find all articles (within the panels)
|
||||
$articles = $section->find('article');
|
||||
if($articles === false || empty($articles))
|
||||
returnServerError('Unable to find articles!');
|
||||
$articles = $section->find('article')
|
||||
or returnServerError('Unable to find articles!');
|
||||
|
||||
// Go through all articles
|
||||
foreach($articles as $article) {
|
||||
|
||||
$anchor = $article->find('h1.review-title a', 0)
|
||||
or returnServerError('Cannot find article URI!');
|
||||
|
||||
$date = $article->find('meta[itemprop=dateCreated]', 0)
|
||||
or returnServerError('Cannot find article date!');
|
||||
|
||||
$rating = $article->find('span.rating', 0)
|
||||
or returnServerError('Cannot find article rating!');
|
||||
|
||||
$summary = $article->find('[itemprop=name]', 0)
|
||||
or returnServerError('Cannot find article summary!');
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['author'] = $this->extractArticleAuthorPosition($article);
|
||||
$item['timestamp'] = $this->extractArticleDate($article);
|
||||
$item['title'] = $this->extractArticleRating($article)
|
||||
$item['timestamp'] = strtotime($date);
|
||||
$item['title'] = $rating->getAttribute('aria-label')
|
||||
. ' : '
|
||||
. $this->extractArticleSummary($article);
|
||||
. strip_tags($summary->innertext);
|
||||
|
||||
$item['uri'] = $this->extractArticleUri($article);
|
||||
$item['uri'] = $anchor->href;
|
||||
|
||||
if($full)
|
||||
if($full) {
|
||||
$item['content'] = $this->extractFullDescription($item['uri']);
|
||||
else
|
||||
} else {
|
||||
$item['content'] = $this->extractArticleDescription($article);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes relative URLs in the given text
|
||||
*/
|
||||
private function fixUrl($text){
|
||||
return preg_replace('/href=(\'|\")\//i', 'href="'.self::URI, $text);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -128,73 +143,11 @@ class KununuBridge extends BridgeAbstract {
|
||||
$company = trim($company);
|
||||
$company = str_replace(' ', '-', $company);
|
||||
$company = strtolower($company);
|
||||
return $this->encodeUmlauts($company);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes unmlauts in the given text
|
||||
*/
|
||||
private function encodeUmlauts($text){
|
||||
$umlauts = Array('/ä/','/ö/','/ü/','/Ä/','/Ö/','/Ü/','/ß/');
|
||||
$replace = Array('ae','oe','ue','Ae','Oe','Ue','ss');
|
||||
|
||||
return preg_replace($umlauts, $replace, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the company name from the review html
|
||||
*/
|
||||
private function extractCompanyName($html){
|
||||
$company_name = $html->find('h1[itemprop=name]', 0);
|
||||
if(is_null($company_name))
|
||||
returnServerError('Cannot find company name!');
|
||||
|
||||
return $company_name->plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date from a given article
|
||||
*/
|
||||
private function extractArticleDate($article){
|
||||
// They conviniently provide a time attribute for us :)
|
||||
$date = $article->find('meta[itemprop=dateCreated]', 0);
|
||||
if(is_null($date))
|
||||
returnServerError('Cannot find article date!');
|
||||
|
||||
return strtotime($date->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rating from a given article
|
||||
*/
|
||||
private function extractArticleRating($article){
|
||||
$rating = $article->find('span.rating', 0);
|
||||
if(is_null($rating))
|
||||
returnServerError('Cannot find article rating!');
|
||||
|
||||
return $rating->getAttribute('aria-label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the summary from a given article
|
||||
*/
|
||||
private function extractArticleSummary($article){
|
||||
$summary = $article->find('[itemprop=name]', 0);
|
||||
if(is_null($summary))
|
||||
returnServerError('Cannot find article summary!');
|
||||
|
||||
return strip_tags($summary->innertext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI from a given article
|
||||
*/
|
||||
private function extractArticleUri($article){
|
||||
$anchor = $article->find('h1.review-title a', 0);
|
||||
if(is_null($anchor))
|
||||
returnServerError('Cannot find article URI!');
|
||||
|
||||
return self::URI . $anchor->href;
|
||||
return preg_replace($umlauts, $replace, $company);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,9 +155,8 @@ class KununuBridge extends BridgeAbstract {
|
||||
*/
|
||||
private function extractArticleAuthorPosition($article){
|
||||
// We need to parse the user-content manually
|
||||
$user_content = $article->find('div.user-content', 0);
|
||||
if(is_null($user_content))
|
||||
returnServerError('Cannot find user content!');
|
||||
$user_content = $article->find('div.user-content', 0)
|
||||
or returnServerError('Cannot find user content!');
|
||||
|
||||
// Go through all h2 elements to find index of required span (I know... it's stupid)
|
||||
$author_position = 'Unknown';
|
||||
@@ -222,11 +174,10 @@ class KununuBridge extends BridgeAbstract {
|
||||
* Returns the description from a given article
|
||||
*/
|
||||
private function extractArticleDescription($article){
|
||||
$description = $article->find('[itemprop=reviewBody]', 0);
|
||||
if(is_null($description))
|
||||
returnServerError('Cannot find article description!');
|
||||
$description = $article->find('[itemprop=reviewBody]', 0)
|
||||
or returnServerError('Cannot find article description!');
|
||||
|
||||
return $this->fixUrl($description->innertext);
|
||||
return $description->innertext;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,14 +185,14 @@ class KununuBridge extends BridgeAbstract {
|
||||
*/
|
||||
private function extractFullDescription($uri){
|
||||
// Load full article
|
||||
$html = getSimpleHTMLDOMCached($uri);
|
||||
if($html === false)
|
||||
returnServerError('Could not load full description!');
|
||||
$html = getSimpleHTMLDOMCached($uri)
|
||||
or returnServerError('Could not load full description!');
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
// Find the article
|
||||
$article = $html->find('article', 0);
|
||||
if(is_null($article))
|
||||
returnServerError('Cannot find article!');
|
||||
$article = $html->find('article', 0)
|
||||
or returnServerError('Cannot find article!');
|
||||
|
||||
// Luckily they use the same layout for the review overview and full article pages :)
|
||||
return $this->extractArticleDescription($article);
|
||||
|
@@ -362,7 +362,7 @@ class LeBonCoinBridge extends BridgeAbstract {
|
||||
);
|
||||
|
||||
$opts = array(
|
||||
CURL_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => $data
|
||||
|
||||
);
|
||||
@@ -424,7 +424,7 @@ class LeBonCoinBridge extends BridgeAbstract {
|
||||
|
||||
$requestJson = new StdClass();
|
||||
$requestJson->owner_type = $this->getInput('owner');
|
||||
$requestJson->filters->location = array();
|
||||
$requestJson->filters = new StdClass();
|
||||
|
||||
$requestJson->filters->keywords = array(
|
||||
'text' => $this->getInput('keywords')
|
||||
|
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;
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,88 +0,0 @@
|
||||
<?php
|
||||
class SexactuBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Riduidel';
|
||||
const NAME = 'Sexactu';
|
||||
const AUTHOR = 'Maïa Mazaurette';
|
||||
const URI = 'http://www.gqmagazine.fr';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Sexactu via rss-bridge';
|
||||
|
||||
const REPLACED_ATTRIBUTES = array(
|
||||
'href' => 'href',
|
||||
'src' => 'src',
|
||||
'data-original' => 'src'
|
||||
);
|
||||
|
||||
public function getURI(){
|
||||
return self::URI . '/sexactu';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
|
||||
$sexactu = $html->find('.container_sexactu', 0);
|
||||
$rowList = $sexactu->find('.row');
|
||||
foreach($rowList as $row) {
|
||||
// only use first list as second one only contains pages numbers
|
||||
|
||||
$title = $row->find('.title', 0);
|
||||
if($title) {
|
||||
$item = array();
|
||||
$item['author'] = self::AUTHOR;
|
||||
$item['title'] = $title->plaintext;
|
||||
$urlAttribute = 'data-href';
|
||||
$uri = $title->$urlAttribute;
|
||||
if($uri === false)
|
||||
continue;
|
||||
if(substr($uri, 0, 1) === 'h') { // absolute uri
|
||||
$item['uri'] = $uri;
|
||||
} else if(substr($uri, 0, 1) === '/') { // domain relative url
|
||||
$item['uri'] = self::URI . $uri;
|
||||
} else {
|
||||
$item['uri'] = $this->getURI() . $uri;
|
||||
}
|
||||
$article = $this->loadFullArticle($item['uri']);
|
||||
$item['content'] = $this->replaceUriInHtmlElement($article->find('.article_content', 0));
|
||||
|
||||
$publicationDate = $article->find('time[itemprop=datePublished]', 0);
|
||||
$short_date = $publicationDate->datetime;
|
||||
$item['timestamp'] = strtotime($short_date);
|
||||
} else {
|
||||
// Sometimes we get rubbish, ignore.
|
||||
continue;
|
||||
}
|
||||
$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);
|
||||
|
||||
$content = $html->find('#article', 0);
|
||||
if($content) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
41
bridges/TheYeteeBridge.php
Normal file
41
bridges/TheYeteeBridge.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
class TheYeteeBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Monsieur Poutounours';
|
||||
const NAME = 'TheYetee';
|
||||
const URI = 'https://theyetee.com';
|
||||
const CACHE_TIMEOUT = 14400; // 4 h
|
||||
const DESCRIPTION = 'Fetch daily shirts from The Yetee';
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request The Yetee.');
|
||||
|
||||
$div = $html->find('.hero-col');
|
||||
foreach($div as $element) {
|
||||
|
||||
$item = array();
|
||||
$item['enclosures'] = array();
|
||||
|
||||
$title = $element->find('h2', 0)->plaintext;
|
||||
$item['title'] = $title;
|
||||
|
||||
$author = trim($element->find('div[class=credit]', 0)->plaintext);
|
||||
$item['author'] = $author;
|
||||
|
||||
$uri = $element->find('div[class=controls] a', 0)->href;
|
||||
$item['uri'] = static::URI.$uri;
|
||||
|
||||
$content = '<p>'.$element->find('section[class=product-listing-info] p', -1)->plaintext.'</p>';
|
||||
$photos = $element->find('a[class=js-modaal-gallery] img');
|
||||
foreach($photos as $photo) {
|
||||
$content = $content."<br /><img src='$photo->src' />";
|
||||
$item['enclosures'][] = $photo->src;
|
||||
}
|
||||
$item['content'] = $content;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
85
bridges/ZoneTelechargementBridge.php
Normal file
85
bridges/ZoneTelechargementBridge.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
class ZoneTelechargementBridge extends BridgeAbstract {
|
||||
const NAME = 'Zone Telechargement';
|
||||
const URI = 'https://ww4.zone-telechargement1.org/';
|
||||
const DESCRIPTION = 'Suivi de série sur Zone Telechargement';
|
||||
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://ww4.zone-telechargement1.org/',
|
||||
'exampleValue' => 'telecharger-series/31079-halt-and-catch-fire-saison-4-french-hd720p.html'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
|
||||
or returnServerError('Could not request Zone Telechargement.');
|
||||
|
||||
// Get the TV show title
|
||||
$qualityselector = 'div[style=font-size: 18px;margin: 10px auto;color:red;font-weight:bold;text-align:center;]';
|
||||
$show = trim($html->find('div[class=smallsep]', 0)->next_sibling()->plaintext);
|
||||
$quality = trim(explode("\n", $html->find($qualityselector, 0)->plaintext)[0]);
|
||||
$this->showTitle = $show . ' ' . $quality;
|
||||
|
||||
// Get the post content
|
||||
$linkshtml = $html->find('div[class=postinfo]', 0);
|
||||
|
||||
$episodes = array();
|
||||
|
||||
$list = $linkshtml->find('a');
|
||||
// Construct the tabble of episodes using the links
|
||||
foreach($list as $element) {
|
||||
// Retrieve episode number from link text
|
||||
$epnumber = explode(' ', $element->plaintext)[1];
|
||||
$hoster = $this->findLinkHoster($element);
|
||||
|
||||
// Format the link and add the link to the corresponding episode table
|
||||
$episodes[$epnumber][] = '<a href="' . $element->href . '">'. $hoster . ' - '
|
||||
. $this->showTitle . ' Episode ' . $epnumber . '</a>';
|
||||
|
||||
}
|
||||
|
||||
// Finally construct the items array
|
||||
foreach($episodes as $epnum => $episode) {
|
||||
$item = array();
|
||||
// Add every link available in the episode table separated by a <br/> tag
|
||||
$item['content'] = implode('<br/>', $episode);
|
||||
$item['title'] = $this->showTitle . ' Episode ' . $epnum;
|
||||
// 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']);
|
||||
// Insert the episode at the beginning of the item list, to show the newest episode first
|
||||
array_unshift($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 findLinkHoster($element)
|
||||
{
|
||||
// The hoster name is one level higher than the link tag : get the parent element
|
||||
$element = $element->parent();
|
||||
//echo "PARENT : $element \n";
|
||||
$continue = true;
|
||||
// Walk through all elements in the reverse order until finding the one with a div and that is not a <br/>
|
||||
while(!($element->find('div', 0) != null && $element->tag != 'br')) {
|
||||
$element = $element->prev_sibling();
|
||||
}
|
||||
// Return the text of the div : it's the file hoster name !
|
||||
return $element->find('div', 0)->plaintext;
|
||||
|
||||
}
|
||||
}
|
@@ -11,7 +11,7 @@ class AtomFormat extends FormatAbstract{
|
||||
$httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
|
||||
$httpInfo = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';
|
||||
|
||||
$serverRequestUri = $this->xml_encode($_SERVER['REQUEST_URI']);
|
||||
$serverRequestUri = isset($_SERVER['REQUEST_URI']) ? $this->xml_encode($_SERVER['REQUEST_URI']) : '';
|
||||
|
||||
$extraInfos = $this->getExtraInfos();
|
||||
$title = $this->xml_encode($extraInfos['name']);
|
||||
|
@@ -85,6 +85,8 @@ EOD;
|
||||
<meta charset="{$charset}">
|
||||
<title>{$title}</title>
|
||||
<link href="static/HtmlFormat.css" rel="stylesheet">
|
||||
<link rel="alternate" type="application/atom+xml" title="Atom" href="./?{$atomquery}" />
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS" href="/?{$mrssquery}" />
|
||||
<meta name="robots" content="noindex, follow">
|
||||
</head>
|
||||
<body>
|
||||
|
@@ -10,7 +10,7 @@ class MrssFormat extends FormatAbstract {
|
||||
$httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
|
||||
$httpInfo = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';
|
||||
|
||||
$serverRequestUri = $this->xml_encode($_SERVER['REQUEST_URI']);
|
||||
$serverRequestUri = isset($_SERVER['REQUEST_URI']) ? $this->xml_encode($_SERVER['REQUEST_URI']) : '';
|
||||
|
||||
$extraInfos = $this->getExtraInfos();
|
||||
$title = $this->xml_encode($extraInfos['name']);
|
||||
@@ -79,6 +79,8 @@ EOD;
|
||||
|
||||
$charset = $this->getCharset();
|
||||
|
||||
/* xml attributes need to have certain characters escaped to be w3c compliant */
|
||||
$imageTitle = htmlspecialchars($title, ENT_COMPAT);
|
||||
/* Data are prepared, now let's begin the "MAGIE !!!" */
|
||||
$toReturn = <<<EOD
|
||||
<?xml version="1.0" encoding="{$charset}"?>
|
||||
@@ -90,7 +92,7 @@ xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<title>{$title}</title>
|
||||
<link>http{$https}://{$httpHost}{$httpInfo}/</link>
|
||||
<description>{$title}</description>
|
||||
<image url="{$icon}" title="{$title}" link="{$uri}"/>
|
||||
<image url="{$icon}" title="{$imageTitle}" link="{$uri}"/>
|
||||
<atom:link rel="alternate" type="text/html" href="{$uri}" />
|
||||
<atom:link rel="self" href="http{$https}://{$httpHost}{$serverRequestUri}" />
|
||||
{$items}
|
||||
|
135
index.php
135
index.php
@@ -178,50 +178,135 @@ try {
|
||||
define('NOPROXY', true);
|
||||
}
|
||||
|
||||
// Custom cache timeout
|
||||
// Cache timeout
|
||||
$cache_timeout = -1;
|
||||
if(array_key_exists('_cache_timeout', $params)) {
|
||||
|
||||
if(!CUSTOM_CACHE_TIMEOUT) {
|
||||
throw new \HttpException('This server doesn\'t support "_cache_timeout"!');
|
||||
}
|
||||
|
||||
$cache_timeout = filter_var($params['_cache_timeout'], FILTER_VALIDATE_INT);
|
||||
|
||||
} else {
|
||||
$cache_timeout = $bridge->getCacheTimeout();
|
||||
}
|
||||
|
||||
// Remove parameters that don't concern bridges
|
||||
$bridge_params = array_diff_key(
|
||||
$params,
|
||||
array_fill_keys(
|
||||
array(
|
||||
'action',
|
||||
'bridge',
|
||||
'format',
|
||||
'_noproxy',
|
||||
'_cache_timeout',
|
||||
), '')
|
||||
);
|
||||
|
||||
// Remove parameters that don't concern caches
|
||||
$cache_params = array_diff_key(
|
||||
$params,
|
||||
array_fill_keys(
|
||||
array(
|
||||
'action',
|
||||
'format',
|
||||
'_noproxy',
|
||||
'_cache_timeout',
|
||||
), '')
|
||||
);
|
||||
|
||||
// Initialize cache
|
||||
$cache = Cache::create('FileCache');
|
||||
$cache->setPath(CACHE_DIR);
|
||||
$cache->purgeCache(86400); // 24 hours
|
||||
$cache->setParameters($params);
|
||||
$cache->setParameters($cache_params);
|
||||
|
||||
unset($params['action']);
|
||||
unset($params['bridge']);
|
||||
unset($params['format']);
|
||||
unset($params['_noproxy']);
|
||||
unset($params['_cache_timeout']);
|
||||
$items = array();
|
||||
$infos = array();
|
||||
$mtime = $cache->getTime();
|
||||
|
||||
if($mtime !== false
|
||||
&& (time() - $cache_timeout < $mtime)
|
||||
&& (!defined('DEBUG') || DEBUG !== true)) { // Load cached data
|
||||
|
||||
// Send "Not Modified" response if client supports it
|
||||
// Implementation based on https://stackoverflow.com/a/10847262
|
||||
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
||||
$stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
|
||||
|
||||
if($mtime <= $stime) { // Cached data is older or same
|
||||
header('HTTP/1.1 304 Not Modified');
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
$cached = $cache->loadData();
|
||||
|
||||
if(isset($cached['items']) && isset($cached['extraInfos'])) {
|
||||
$items = $cached['items'];
|
||||
$infos = $cached['extraInfos'];
|
||||
}
|
||||
|
||||
} else { // Collect new data
|
||||
|
||||
try {
|
||||
$bridge->setDatas($bridge_params);
|
||||
$bridge->collectData();
|
||||
|
||||
$items = $bridge->getItems();
|
||||
$infos = array(
|
||||
'name' => $bridge->getName(),
|
||||
'uri' => $bridge->getURI(),
|
||||
'icon' => $bridge->getIcon()
|
||||
);
|
||||
} catch(Error $e) {
|
||||
$item = array();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
$params['_error_time'] = urlencode((int)(time() / 86400));
|
||||
|
||||
// Error 0 is a special case (i.e. "trying to get property of non-object")
|
||||
if($e->getCode() === 0) {
|
||||
$item['title'] = 'Bridge encountered an unexpected situation! (' . $params['_error_time'] . ')';
|
||||
} else {
|
||||
$item['title'] = 'Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')';
|
||||
}
|
||||
|
||||
$item['uri'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
|
||||
$item['timestamp'] = time();
|
||||
$item['content'] = buildBridgeException($e, $bridge);
|
||||
|
||||
$items[] = $item;
|
||||
} catch(Exception $e) {
|
||||
$item = array();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
$params['_error_time'] = urlencode((int)(time() / 86400));
|
||||
|
||||
$item['uri'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
|
||||
$item['title'] = 'Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')';
|
||||
$item['timestamp'] = time();
|
||||
$item['content'] = buildBridgeException($e, $bridge);
|
||||
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
// Store data in cache
|
||||
$cache->saveData(array(
|
||||
'items' => $items,
|
||||
'extraInfos' => $infos
|
||||
));
|
||||
|
||||
// Load cache & data
|
||||
try {
|
||||
$bridge->setCache($cache);
|
||||
$bridge->setCacheTimeout($cache_timeout);
|
||||
$bridge->dieIfNotModified();
|
||||
$bridge->setDatas($params);
|
||||
} catch(Error $e) {
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/html');
|
||||
die(buildBridgeException($e, $bridge));
|
||||
} catch(Exception $e) {
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/html');
|
||||
die(buildBridgeException($e, $bridge));
|
||||
}
|
||||
|
||||
// Data transformation
|
||||
try {
|
||||
$format = Format::create($format);
|
||||
$format->setItems($bridge->getItems());
|
||||
$format->setExtraInfos($bridge->getExtraInfos());
|
||||
$format->setLastModified($bridge->getCacheTime());
|
||||
$format->setItems($items);
|
||||
$format->setExtraInfos($infos);
|
||||
$format->setLastModified($mtime);
|
||||
$format->display();
|
||||
} catch(Error $e) {
|
||||
http_response_code($e->getCode());
|
||||
@@ -230,7 +315,7 @@ try {
|
||||
} catch(Exception $e) {
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/html');
|
||||
die(buildBridgeException($e, $bridge));
|
||||
die(buildTransformException($e, $bridge));
|
||||
}
|
||||
} else {
|
||||
echo BridgeList::create($whitelist_selection, $showInactive);
|
||||
|
@@ -9,23 +9,9 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const PARAMETERS = array();
|
||||
|
||||
protected $cache;
|
||||
protected $extraInfos;
|
||||
protected $items = array();
|
||||
protected $inputs = array();
|
||||
protected $queriedContext = '';
|
||||
protected $cacheTimeout;
|
||||
|
||||
/**
|
||||
* Return cachable datas (extrainfos and items) stored in the bridge
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCachable(){
|
||||
return array(
|
||||
'items' => $this->getItems(),
|
||||
'extraInfos' => $this->getExtraInfos()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return items stored in the bridge
|
||||
@@ -116,91 +102,38 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the context matching the provided inputs
|
||||
*
|
||||
* @param array $inputs Associative array of inputs
|
||||
* @return mixed Returns the context name or null if no match was found
|
||||
*/
|
||||
protected function getQueriedContext(array $inputs){
|
||||
$queriedContexts = array();
|
||||
|
||||
// Detect matching context
|
||||
foreach(static::PARAMETERS as $context => $set) {
|
||||
$queriedContexts[$context] = null;
|
||||
|
||||
// Check if all parameters of the context are satisfied
|
||||
foreach($set as $id => $properties) {
|
||||
if(isset($inputs[$id]) && !empty($inputs[$id])) {
|
||||
$queriedContexts[$context] = true;
|
||||
} elseif(isset($properties['required'])
|
||||
&& $properties['required'] === true) {
|
||||
$queriedContexts[$context] = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Abort if one of the globally required parameters is not satisfied
|
||||
if(array_key_exists('global', static::PARAMETERS)
|
||||
&& $queriedContexts['global'] === false) {
|
||||
return null;
|
||||
}
|
||||
unset($queriedContexts['global']);
|
||||
|
||||
switch(array_sum($queriedContexts)) {
|
||||
case 0: // Found no match, is there a context without parameters?
|
||||
foreach($queriedContexts as $context => $queried) {
|
||||
if(is_null($queried)) {
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case 1: // Found unique match
|
||||
return array_search(true, $queriedContexts);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defined datas with parameters depending choose bridge
|
||||
* Note : you can define a cache with "setCache"
|
||||
* @param array array with expected bridge paramters
|
||||
*/
|
||||
public function setDatas(array $inputs){
|
||||
if(!is_null($this->cache)) {
|
||||
$time = $this->cache->getTime();
|
||||
if($time !== false
|
||||
&& (time() - $this->getCacheTimeout() < $time)
|
||||
&& (!defined('DEBUG') || DEBUG !== true)) {
|
||||
$cached = $this->cache->loadData();
|
||||
if(isset($cached['items']) && isset($cached['extraInfos'])) {
|
||||
$this->items = $cached['items'];
|
||||
$this->extraInfos = $cached['extraInfos'];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(empty(static::PARAMETERS)) {
|
||||
|
||||
if(!empty($inputs)) {
|
||||
returnClientError('Invalid parameters value(s)');
|
||||
}
|
||||
|
||||
$this->collectData();
|
||||
if(!is_null($this->cache)) {
|
||||
$this->cache->saveData($this->getCachable());
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if(!validateData($inputs, static::PARAMETERS)) {
|
||||
returnClientError('Invalid parameters value(s)');
|
||||
$validator = new ParameterValidator();
|
||||
|
||||
if(!$validator->validateData($inputs, static::PARAMETERS)) {
|
||||
$parameters = array_map(
|
||||
function($i){ return $i['name']; }, // Just display parameter names
|
||||
$validator->getInvalidParameters()
|
||||
);
|
||||
|
||||
returnClientError(
|
||||
'Invalid parameters value(s): '
|
||||
. implode(', ', $parameters)
|
||||
);
|
||||
}
|
||||
|
||||
// Guess the paramter context from input data
|
||||
$this->queriedContext = $this->getQueriedContext($inputs);
|
||||
$this->queriedContext = $validator->getQueriedContext($inputs, static::PARAMETERS);
|
||||
if(is_null($this->queriedContext)) {
|
||||
returnClientError('Required parameter(s) missing');
|
||||
} elseif($this->queriedContext === false) {
|
||||
@@ -209,11 +142,6 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
|
||||
$this->setInputs($inputs, $this->queriedContext);
|
||||
|
||||
$this->collectData();
|
||||
|
||||
if(!is_null($this->cache)) {
|
||||
$this->cache->saveData($this->getCachable());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,20 +166,10 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
// Return cached name when bridge is using cached data
|
||||
if(isset($this->extraInfos)) {
|
||||
return $this->extraInfos['name'];
|
||||
}
|
||||
|
||||
return static::NAME;
|
||||
}
|
||||
|
||||
public function getIcon(){
|
||||
// Return cached icon when bridge is using cached data
|
||||
if(isset($this->extraInfos)) {
|
||||
return $this->extraInfos['icon'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -260,59 +178,11 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
// Return cached uri when bridge is using cached data
|
||||
if(isset($this->extraInfos)) {
|
||||
return $this->extraInfos['uri'];
|
||||
}
|
||||
|
||||
return static::URI;
|
||||
}
|
||||
|
||||
public function getExtraInfos(){
|
||||
return array(
|
||||
'name' => $this->getName(),
|
||||
'uri' => $this->getURI(),
|
||||
'icon' => $this->getIcon()
|
||||
);
|
||||
}
|
||||
|
||||
public function setCache(\CacheInterface $cache){
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
public function setCacheTimeout($timeout){
|
||||
if(is_numeric($timeout) && ($timeout < 1 || $timeout > 86400)) {
|
||||
$this->cacheTimeout = static::CACHE_TIMEOUT;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cacheTimeout = $timeout;
|
||||
}
|
||||
|
||||
public function getCacheTimeout(){
|
||||
return isset($this->cacheTimeout) ? $this->cacheTimeout : static::CACHE_TIMEOUT;
|
||||
return static::CACHE_TIMEOUT;
|
||||
}
|
||||
|
||||
public function getCacheTime(){
|
||||
return !is_null($this->cache) ? $this->cache->getTime() : false;
|
||||
}
|
||||
|
||||
public function dieIfNotModified(){
|
||||
if ((defined('DEBUG') && DEBUG === true)) return; // disabled in debug mode
|
||||
|
||||
$if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
|
||||
if (!$if_modified_since) return; // If-Modified-Since value is required
|
||||
|
||||
$last_modified = $this->getCacheTime();
|
||||
if (!$last_modified) return; // did not detect cache time
|
||||
|
||||
if (time() - $this->getCacheTimeout() > $last_modified) return; // cache timeout
|
||||
|
||||
$last_modified = (gmdate('D, d M Y H:i:s ', $last_modified) . 'GMT');
|
||||
|
||||
if ($if_modified_since == $last_modified) {
|
||||
header('HTTP/1.1 304 Not Modified');
|
||||
die();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -39,36 +39,44 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
$parameters = array()) {
|
||||
$form = BridgeCard::getFormHeader($bridgeName, $isHttps);
|
||||
|
||||
foreach($parameters as $id => $inputEntry) {
|
||||
if(!isset($inputEntry['exampleValue']))
|
||||
$inputEntry['exampleValue'] = '';
|
||||
if(count($parameters) > 0) {
|
||||
|
||||
if(!isset($inputEntry['defaultValue']))
|
||||
$inputEntry['defaultValue'] = '';
|
||||
$form .= '<div class="parameters">';
|
||||
|
||||
$idArg = 'arg-'
|
||||
. urlencode($bridgeName)
|
||||
. '-'
|
||||
. urlencode($parameterName)
|
||||
. '-'
|
||||
. urlencode($id);
|
||||
foreach($parameters as $id => $inputEntry) {
|
||||
if(!isset($inputEntry['exampleValue']))
|
||||
$inputEntry['exampleValue'] = '';
|
||||
|
||||
$form .= '<label for="'
|
||||
. $idArg
|
||||
. '">'
|
||||
. filter_var($inputEntry['name'], FILTER_SANITIZE_STRING)
|
||||
. ' : </label>'
|
||||
. PHP_EOL;
|
||||
if(!isset($inputEntry['defaultValue']))
|
||||
$inputEntry['defaultValue'] = '';
|
||||
|
||||
if(!isset($inputEntry['type']) || $inputEntry['type'] === 'text') {
|
||||
$form .= BridgeCard::getTextInput($inputEntry, $idArg, $id);
|
||||
} elseif($inputEntry['type'] === 'number') {
|
||||
$form .= BridgeCard::getNumberInput($inputEntry, $idArg, $id);
|
||||
} else if($inputEntry['type'] === 'list') {
|
||||
$form .= BridgeCard::getListInput($inputEntry, $idArg, $id);
|
||||
} elseif($inputEntry['type'] === 'checkbox') {
|
||||
$form .= BridgeCard::getCheckboxInput($inputEntry, $idArg, $id);
|
||||
$idArg = 'arg-'
|
||||
. urlencode($bridgeName)
|
||||
. '-'
|
||||
. urlencode($parameterName)
|
||||
. '-'
|
||||
. urlencode($id);
|
||||
|
||||
$form .= '<label for="'
|
||||
. $idArg
|
||||
. '">'
|
||||
. filter_var($inputEntry['name'], FILTER_SANITIZE_STRING)
|
||||
. '</label>'
|
||||
. PHP_EOL;
|
||||
|
||||
if(!isset($inputEntry['type']) || $inputEntry['type'] === 'text') {
|
||||
$form .= BridgeCard::getTextInput($inputEntry, $idArg, $id);
|
||||
} elseif($inputEntry['type'] === 'number') {
|
||||
$form .= BridgeCard::getNumberInput($inputEntry, $idArg, $id);
|
||||
} else if($inputEntry['type'] === 'list') {
|
||||
$form .= BridgeCard::getListInput($inputEntry, $idArg, $id);
|
||||
} elseif($inputEntry['type'] === 'checkbox') {
|
||||
$form .= BridgeCard::getCheckboxInput($inputEntry, $idArg, $id);
|
||||
}
|
||||
}
|
||||
|
||||
$form .= '</div>';
|
||||
|
||||
}
|
||||
|
||||
if($isActive) {
|
||||
@@ -106,7 +114,7 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
. filter_var($entry['exampleValue'], FILTER_SANITIZE_STRING)
|
||||
. '" name="'
|
||||
. $name
|
||||
. '" /><br>'
|
||||
. '" />'
|
||||
. PHP_EOL;
|
||||
}
|
||||
|
||||
@@ -121,7 +129,7 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
. filter_var($entry['exampleValue'], FILTER_SANITIZE_NUMBER_INT)
|
||||
. '" name="'
|
||||
. $name
|
||||
. '" /><br>'
|
||||
. '" />'
|
||||
. PHP_EOL;
|
||||
}
|
||||
|
||||
@@ -172,7 +180,7 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
}
|
||||
}
|
||||
|
||||
$list .= '</select><br>';
|
||||
$list .= '</select>';
|
||||
|
||||
return $list;
|
||||
}
|
||||
@@ -186,7 +194,7 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
. $name
|
||||
. '" '
|
||||
. ($entry['defaultValue'] === 'checked' ?: '')
|
||||
. ' /><br>'
|
||||
. ' />'
|
||||
. PHP_EOL;
|
||||
}
|
||||
|
||||
|
@@ -6,13 +6,6 @@ interface BridgeInterface {
|
||||
*/
|
||||
public function collectData();
|
||||
|
||||
/**
|
||||
* Returns an array of cachable elements
|
||||
*
|
||||
* @return array Associative array of cachable elements
|
||||
*/
|
||||
public function getCachable();
|
||||
|
||||
/**
|
||||
* Returns the description
|
||||
*
|
||||
@@ -20,13 +13,6 @@ interface BridgeInterface {
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
/**
|
||||
* Return an array of extra information
|
||||
*
|
||||
* @return array Associative array of extra information
|
||||
*/
|
||||
public function getExtraInfos();
|
||||
|
||||
/**
|
||||
* Returns an array of collected items
|
||||
*
|
||||
@@ -69,22 +55,6 @@ interface BridgeInterface {
|
||||
*/
|
||||
public function getURI();
|
||||
|
||||
/**
|
||||
* Sets the cache instance
|
||||
*
|
||||
* @param object CacheInterface The cache instance
|
||||
*/
|
||||
public function setCache(\CacheInterface $cache);
|
||||
|
||||
/**
|
||||
* Sets the timeout for clearing the cache files. The timeout must be
|
||||
* specified between 1..86400 seconds (max. 24 hours). The default timeout
|
||||
* (specified by the bridge maintainer) applies for invalid values.
|
||||
*
|
||||
* @param int $timeout The cache timeout in seconds
|
||||
*/
|
||||
public function setCacheTimeout($timeout);
|
||||
|
||||
/**
|
||||
* Returns the cache timeout
|
||||
*
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class Configuration {
|
||||
|
||||
public static $VERSION = '2018-09-09';
|
||||
public static $VERSION = '2018-10-15';
|
||||
|
||||
public static $config = null;
|
||||
|
||||
|
@@ -69,14 +69,18 @@ function buildBridgeException($e, $bridge){
|
||||
. Configuration::getVersion()
|
||||
. '`';
|
||||
|
||||
$body_html = nl2br($body);
|
||||
$link = buildGitHubIssueQuery($title, $body, 'bug report', $bridge->getMaintainer());
|
||||
|
||||
$header = buildHeader($e, $bridge);
|
||||
$message = "<strong>{$bridge->getName()}</strong> was
|
||||
unable to receive or process the remote website's content!";
|
||||
$message = <<<EOD
|
||||
<strong>{$bridge->getName()}</strong> was unable to receive or process the
|
||||
remote website's content!<br>
|
||||
{$body_html}
|
||||
EOD;
|
||||
$section = buildSection($e, $bridge, $message, $link);
|
||||
|
||||
return buildPage($title, $header, $section);
|
||||
return $section;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +131,7 @@ function buildSection($e, $bridge, $message, $link){
|
||||
<ul class="advice">
|
||||
<li>Press Return to check your input parameters</li>
|
||||
<li>Press F5 to retry</li>
|
||||
<li>Open a GitHub Issue if this error persists</li>
|
||||
<li>Open a <a href="{$link}">GitHub Issue</a> if this error persists</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a href="{$link}" title="After clicking this button you can review
|
||||
|
171
lib/ParameterValidator.php
Normal file
171
lib/ParameterValidator.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* Implements a validator for bridge parameters
|
||||
*/
|
||||
class ParameterValidator {
|
||||
private $invalid = array();
|
||||
|
||||
private function addInvalidParameter($name, $reason){
|
||||
$this->invalid[] = array(
|
||||
'name' => $name,
|
||||
'reason' => $reason
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of invalid parameters, where each element is an
|
||||
* array of 'name' and 'reason'.
|
||||
*/
|
||||
public function getInvalidParameters() {
|
||||
return $this->invalid;
|
||||
}
|
||||
|
||||
private function validateTextValue($value, $pattern = null){
|
||||
if(!is_null($pattern)) {
|
||||
$filteredValue = filter_var($value,
|
||||
FILTER_VALIDATE_REGEXP,
|
||||
array('options' => array(
|
||||
'regexp' => '/^' . $pattern . '$/'
|
||||
)
|
||||
));
|
||||
} else {
|
||||
$filteredValue = filter_var($value);
|
||||
}
|
||||
|
||||
if($filteredValue === false)
|
||||
return null;
|
||||
|
||||
return $filteredValue;
|
||||
}
|
||||
|
||||
private function validateNumberValue($value){
|
||||
$filteredValue = filter_var($value, FILTER_VALIDATE_INT);
|
||||
|
||||
if($filteredValue === false)
|
||||
return null;
|
||||
|
||||
return $filteredValue;
|
||||
}
|
||||
|
||||
private function validateCheckboxValue($value){
|
||||
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
}
|
||||
|
||||
private function validateListValue($value, $expectedValues){
|
||||
$filteredValue = filter_var($value);
|
||||
|
||||
if($filteredValue === false)
|
||||
return null;
|
||||
|
||||
if(!in_array($filteredValue, $expectedValues)) { // Check sub-values?
|
||||
foreach($expectedValues as $subName => $subValue) {
|
||||
if(is_array($subValue) && in_array($filteredValue, $subValue))
|
||||
return $filteredValue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return $filteredValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all required parameters are supplied by the user
|
||||
* @param $data An array of parameters provided by the user
|
||||
* @param $parameters An array of bridge parameters
|
||||
*/
|
||||
public function validateData(&$data, $parameters){
|
||||
|
||||
if(!is_array($data))
|
||||
return false;
|
||||
|
||||
foreach($data as $name => $value) {
|
||||
$registered = false;
|
||||
foreach($parameters as $context => $set) {
|
||||
if(array_key_exists($name, $set)) {
|
||||
$registered = true;
|
||||
if(!isset($set[$name]['type'])) {
|
||||
$set[$name]['type'] = 'text';
|
||||
}
|
||||
|
||||
switch($set[$name]['type']) {
|
||||
case 'number':
|
||||
$data[$name] = $this->validateNumberValue($value);
|
||||
break;
|
||||
case 'checkbox':
|
||||
$data[$name] = $this->validateCheckboxValue($value);
|
||||
break;
|
||||
case 'list':
|
||||
$data[$name] = $this->validateListValue($value, $set[$name]['values']);
|
||||
break;
|
||||
default:
|
||||
case 'text':
|
||||
if(isset($set[$name]['pattern'])) {
|
||||
$data[$name] = $this->validateTextValue($value, $set[$name]['pattern']);
|
||||
} else {
|
||||
$data[$name] = $this->validateTextValue($value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(is_null($data[$name]) && isset($set[$name]['required']) && $set[$name]['required']) {
|
||||
$this->addInvalidParameter($name, 'Parameter is invalid!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!$registered) {
|
||||
$this->addInvalidParameter($name, 'Parameter is not registered!');
|
||||
}
|
||||
}
|
||||
|
||||
return empty($this->invalid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the context matching the provided inputs
|
||||
*
|
||||
* @param array $data Associative array of user data
|
||||
* @param array $parameters Array of bridge parameters
|
||||
* @return mixed Returns the context name or null if no match was found
|
||||
*/
|
||||
public function getQueriedContext($data, $parameters){
|
||||
$queriedContexts = array();
|
||||
|
||||
// Detect matching context
|
||||
foreach($parameters as $context => $set) {
|
||||
$queriedContexts[$context] = null;
|
||||
|
||||
// Check if all parameters of the context are satisfied
|
||||
foreach($set as $id => $properties) {
|
||||
if(isset($data[$id]) && !empty($data[$id])) {
|
||||
$queriedContexts[$context] = true;
|
||||
} elseif(isset($properties['required'])
|
||||
&& $properties['required'] === true) {
|
||||
$queriedContexts[$context] = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Abort if one of the globally required parameters is not satisfied
|
||||
if(array_key_exists('global', $parameters)
|
||||
&& $queriedContexts['global'] === false) {
|
||||
return null;
|
||||
}
|
||||
unset($queriedContexts['global']);
|
||||
|
||||
switch(array_sum($queriedContexts)) {
|
||||
case 0: // Found no match, is there a context without parameters?
|
||||
foreach($queriedContexts as $context => $queried) {
|
||||
if(is_null($queried)) {
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case 1: // Found unique match
|
||||
return array_search(true, $queriedContexts);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -18,8 +18,8 @@ require __DIR__ . '/Authentication.php';
|
||||
require __DIR__ . '/Configuration.php';
|
||||
require __DIR__ . '/BridgeCard.php';
|
||||
require __DIR__ . '/BridgeList.php';
|
||||
require __DIR__ . '/ParameterValidator.php';
|
||||
|
||||
require __DIR__ . '/validation.php';
|
||||
require __DIR__ . '/html.php';
|
||||
require __DIR__ . '/error.php';
|
||||
require __DIR__ . '/contents.php';
|
||||
|
@@ -32,19 +32,26 @@ function getContents($url, $header = array(), $opts = array()){
|
||||
debugMessage('Cant\'t download ' . $url . ' cUrl error: ' . $curlError . ' (' . $curlErrno . ')');
|
||||
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$errorCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$header = substr($data, 0, $headerSize);
|
||||
$headers = parseResponseHeader($header);
|
||||
$finalHeader = end($headers);
|
||||
|
||||
if(array_key_exists('http_code', $finalHeader)
|
||||
&& strpos($finalHeader['http_code'], '200') === false
|
||||
&& array_key_exists('Server', $finalHeader)
|
||||
&& strpos($finalHeader['Server'], 'cloudflare') !== false) {
|
||||
returnServerError(<<< EOD
|
||||
The server responded with a Cloudflare challenge, which is not supported by RSS-Bridge!<br>
|
||||
if($errorCode !== 200) {
|
||||
|
||||
if(array_key_exists('Server', $finalHeader) && strpos($finalHeader['Server'], 'cloudflare') !== false) {
|
||||
returnServerError(<<< EOD
|
||||
The server responded with a Cloudflare challenge, which is not supported by RSS-Bridge!
|
||||
If this error persists longer than a week, please consider opening an issue on GitHub!
|
||||
EOD
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
returnError(<<<EOD
|
||||
The requested resouce cannot be found!
|
||||
Please make sure your input parameters are correct!
|
||||
EOD
|
||||
, $errorCode);
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
@@ -163,13 +170,15 @@ function getMimeType($url) {
|
||||
static $mime = null;
|
||||
|
||||
if (is_null($mime)) {
|
||||
// Default values, overriden by /etc/mime.types when present
|
||||
$mime = array(
|
||||
'jpg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
'image' => 'image/*'
|
||||
);
|
||||
if (is_file('/etc/mime.types')) {
|
||||
// '@' is used to mute open_basedir warning, see issue #818
|
||||
if (@is_readable('/etc/mime.types')) {
|
||||
$file = fopen('/etc/mime.types', 'r');
|
||||
while(($line = fgets($file)) !== false) {
|
||||
$line = trim(preg_replace('/#.*/', '', $line));
|
||||
|
@@ -1,95 +0,0 @@
|
||||
<?php
|
||||
function validateData(&$data, $parameters){
|
||||
$validateTextValue = function($value, $pattern = null){
|
||||
if(!is_null($pattern)) {
|
||||
$filteredValue = filter_var($value,
|
||||
FILTER_VALIDATE_REGEXP,
|
||||
array('options' => array(
|
||||
'regexp' => '/^' . $pattern . '$/'
|
||||
)
|
||||
));
|
||||
} else {
|
||||
$filteredValue = filter_var($value);
|
||||
}
|
||||
|
||||
if($filteredValue === false)
|
||||
return null;
|
||||
|
||||
return $filteredValue;
|
||||
};
|
||||
|
||||
$validateNumberValue = function($value){
|
||||
$filteredValue = filter_var($value, FILTER_VALIDATE_INT);
|
||||
|
||||
if($filteredValue === false)
|
||||
return null;
|
||||
|
||||
return $filteredValue;
|
||||
};
|
||||
|
||||
$validateCheckboxValue = function($value){
|
||||
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
};
|
||||
|
||||
$validateListValue = function($value, $expectedValues){
|
||||
$filteredValue = filter_var($value);
|
||||
|
||||
if($filteredValue === false)
|
||||
return null;
|
||||
|
||||
if(!in_array($filteredValue, $expectedValues)) { // Check sub-values?
|
||||
foreach($expectedValues as $subName => $subValue) {
|
||||
if(is_array($subValue) && in_array($filteredValue, $subValue))
|
||||
return $filteredValue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return $filteredValue;
|
||||
};
|
||||
|
||||
if(!is_array($data))
|
||||
return false;
|
||||
|
||||
foreach($data as $name => $value) {
|
||||
$registered = false;
|
||||
foreach($parameters as $context => $set) {
|
||||
if(array_key_exists($name, $set)) {
|
||||
$registered = true;
|
||||
if(!isset($set[$name]['type'])) {
|
||||
$set[$name]['type'] = 'text';
|
||||
}
|
||||
|
||||
switch($set[$name]['type']) {
|
||||
case 'number':
|
||||
$data[$name] = $validateNumberValue($value);
|
||||
break;
|
||||
case 'checkbox':
|
||||
$data[$name] = $validateCheckboxValue($value);
|
||||
break;
|
||||
case 'list':
|
||||
$data[$name] = $validateListValue($value, $set[$name]['values']);
|
||||
break;
|
||||
default:
|
||||
case 'text':
|
||||
if(isset($set[$name]['pattern'])) {
|
||||
$data[$name] = $validateTextValue($value, $set[$name]['pattern']);
|
||||
} else {
|
||||
$data[$name] = $validateTextValue($value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(is_null($data[$name]) && isset($set[$name]['required']) && $set[$name]['required']) {
|
||||
echo 'Parameter \'' . $name . '\' is invalid!' . PHP_EOL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!$registered)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
189
static/style.css
189
static/style.css
@@ -7,12 +7,21 @@ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockq
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
|
||||
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Adjust parameters for browsers that don't support the grid layout */
|
||||
|
||||
.parameters label:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Let's go for the actual style */
|
||||
body {
|
||||
body {
|
||||
background-color: #f0f0f0;
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
|
||||
}
|
||||
@@ -23,37 +32,43 @@ a, a:link, a:visited {
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
header {
|
||||
|
||||
header {
|
||||
margin-top: 40px;
|
||||
text-align: center;
|
||||
color: #1182DB;
|
||||
}
|
||||
header > h1 {
|
||||
|
||||
header > h1 {
|
||||
font-size: 500%;
|
||||
font-weight: bold;
|
||||
}
|
||||
header > h2 {
|
||||
|
||||
header > h2 {
|
||||
margin-left: 1em;
|
||||
font-size: 200%;
|
||||
}
|
||||
header > section.warning {
|
||||
|
||||
header > section.warning {
|
||||
width: 40%;
|
||||
background-color: #ffc600;
|
||||
color: #5f5f5f;
|
||||
}
|
||||
header > section.critical-warning {
|
||||
width: 40%;
|
||||
background-color: #cf3e3e;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Input boxes */
|
||||
input[type="text"] {
|
||||
header > section.critical-warning {
|
||||
width: 40%;
|
||||
background-color: #cf3e3e;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
select,
|
||||
input[type="text"],
|
||||
input[type="number"] {
|
||||
background-color: white;
|
||||
color: #404552;
|
||||
border: 1px solid #dedede;
|
||||
@@ -61,30 +76,39 @@ a:hover {
|
||||
margin-bottom: 10px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
input[type="text"]:focus {
|
||||
|
||||
select:focus,
|
||||
input[type="text"]:focus,
|
||||
input[type="number"]:focus {
|
||||
outline: none;
|
||||
border-color: #888;
|
||||
}
|
||||
.searchbar {
|
||||
|
||||
.searchbar {
|
||||
width: 40%;
|
||||
margin: 40px auto 100px;
|
||||
}
|
||||
.searchbar input[type="text"] {
|
||||
|
||||
.searchbar input[type="text"] {
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
font-size: 1.1em;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.searchbar input[type="text"]::placeholder {
|
||||
|
||||
.searchbar input[type="text"]::placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
.searchbar input[type="text"]:focus::-webkit-input-placeholder { opacity: 0; }
|
||||
.searchbar input[type="text"]:focus::-moz-placeholder { opacity: 0; }
|
||||
.searchbar input[type="text"]:focus:-moz-placeholder { opacity: 0; }
|
||||
.searchbar input[type="text"]:focus:-ms-input-placeholder { opacity: 0; }
|
||||
|
||||
.searchbar > h3 {
|
||||
.searchbar input[type="text"]:focus::-webkit-input-placeholder,
|
||||
.searchbar input[type="text"]:focus::-moz-placeholder,
|
||||
.searchbar input[type="text"]:focus:-moz-placeholder,
|
||||
.searchbar input[type="text"]:focus:-ms-input-placeholder {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.searchbar > h3 {
|
||||
font-size: 200%;
|
||||
font-weight: bold;
|
||||
color: #1182DB;
|
||||
@@ -92,7 +116,7 @@ input[type="text"]:focus {
|
||||
}
|
||||
|
||||
/* Section */
|
||||
section {
|
||||
section {
|
||||
background-color: #FFFFFF;
|
||||
width: 60%;
|
||||
margin: 30px auto;
|
||||
@@ -101,20 +125,26 @@ input[type="text"]:focus {
|
||||
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.09);
|
||||
border-radius: 4px;
|
||||
}
|
||||
section.footer {
|
||||
|
||||
section.footer {
|
||||
opacity: 0.5;
|
||||
}
|
||||
section.footer:hover {
|
||||
|
||||
section.footer:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
section > h2 {
|
||||
|
||||
section.footer .version {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
section > h2 {
|
||||
font-size: 200%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
/* Buttons */
|
||||
button {
|
||||
button {
|
||||
line-height: 1.9em;
|
||||
color: #FFF;
|
||||
font-weight: bold;
|
||||
@@ -127,30 +157,82 @@ input[type="text"]:focus {
|
||||
cursor: pointer;
|
||||
width: calc(20% - 4px);
|
||||
}
|
||||
button.small {
|
||||
|
||||
button.small {
|
||||
width: auto;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
button:hover {
|
||||
background: #49afff;
|
||||
}
|
||||
|
||||
.description {
|
||||
button:hover {
|
||||
background: #49afff;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 10px;
|
||||
}
|
||||
h5 {
|
||||
|
||||
h5 {
|
||||
margin: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
form {
|
||||
|
||||
form {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.maintainer {
|
||||
|
||||
.parameters label::first-letter {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.parameters label::after {
|
||||
content: ' :';
|
||||
}
|
||||
|
||||
@supports (display: grid) {
|
||||
|
||||
.parameters {
|
||||
display: grid;
|
||||
padding: 12px 0;
|
||||
grid-template-columns: 40% max-content;
|
||||
grid-column-gap: 10px;
|
||||
grid-row-gap: 5px;
|
||||
}
|
||||
|
||||
.parameters label {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.parameters label::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.parameters input[type="text"],
|
||||
.parameters input[type="number"],
|
||||
.parameters input[type="checkbox"],
|
||||
.parameters select {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.parameters input[type="text"],
|
||||
.parameters input[type="number"] {
|
||||
width: auto;
|
||||
color: #404552;
|
||||
}
|
||||
|
||||
.parameters input[type="checkbox"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
} /* @supports (display: grid) */
|
||||
|
||||
.maintainer {
|
||||
color: #888888;
|
||||
font-size: 70%;
|
||||
text-align: right;
|
||||
}
|
||||
.secure-warning {
|
||||
|
||||
.secure-warning {
|
||||
background-color: #ffc600;
|
||||
color: #5f5f5f;
|
||||
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3);
|
||||
@@ -160,7 +242,8 @@ input[type="text"]:focus {
|
||||
margin: auto;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
form {
|
||||
|
||||
form {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -169,15 +252,16 @@ select {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
h5 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show more / less */
|
||||
.showmore-box {
|
||||
.showmore-box {
|
||||
display: none;
|
||||
}
|
||||
.showmore, .showless {
|
||||
|
||||
.showmore, .showless {
|
||||
color: #888888;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -185,18 +269,21 @@ select {
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
}
|
||||
.showmore-box:checked ~ .showmore {
|
||||
|
||||
.showmore-box:checked ~ .showmore {
|
||||
display: none;
|
||||
}
|
||||
.showmore-box:not(:checked) ~ .showless {
|
||||
|
||||
.showmore-box:not(:checked) ~ .showless {
|
||||
display: none;
|
||||
}
|
||||
.showmore-box:checked ~ form, .showmore-box:checked ~ h5 {
|
||||
|
||||
.showmore-box:checked ~ form, .showmore-box:checked ~ h5 {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Additional styles for error pages */
|
||||
.exception-message {
|
||||
.exception-message {
|
||||
background-color: #c00000;
|
||||
color: #FFFFFF;
|
||||
font-weight: bold;
|
||||
@@ -207,11 +294,13 @@ select {
|
||||
margin: auto;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.advice {
|
||||
|
||||
.advice {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: table;
|
||||
}
|
||||
.advice > li {
|
||||
|
||||
.advice > li {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user