mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-15 21:14:07 +02:00
Compare commits
155 Commits
2017-08-03
...
2018-06-10
Author | SHA1 | Date | |
---|---|---|---|
|
4c5013bc82 | ||
|
7dc09db9ca | ||
|
d92da8f0f7 | ||
|
064ba456e8 | ||
|
8ac8e08abf | ||
|
c4f32c31a8 | ||
|
4369e077c2 | ||
|
1045850043 | ||
|
2d8f4dc3c5 | ||
|
779b638fb4 | ||
|
3ca59392c2 | ||
|
9b34b68180 | ||
|
79ebdc4b39 | ||
|
8770c87389 | ||
|
c1e3352218 | ||
|
00570ce1b4 | ||
|
df33dcff4e | ||
|
e60b5ab193 | ||
|
b0c7a62f74 | ||
|
57b15a089e | ||
|
4b7fbe4188 | ||
|
2390fb58b3 | ||
|
1e8d29f6ec | ||
|
644d13686c | ||
|
aa0ff1c9b1 | ||
|
539d9f1f06 | ||
|
5ece801ce7 | ||
|
4dcea6d9c9 | ||
|
d69e2521f1 | ||
|
7927d73719 | ||
|
0620f30ae0 | ||
|
795494cfce | ||
|
ba8542156c | ||
|
55f112e034 | ||
|
208fff801d | ||
|
3c9860de43 | ||
|
a16ec196c5 | ||
|
887fc7b037 | ||
|
1bd4a40f71 | ||
|
494169f959 | ||
|
178177e787 | ||
|
1cb83ccea3 | ||
|
c899399569 | ||
|
0f93370e92 | ||
|
45c3dcb636 | ||
|
ecfc220b10 | ||
|
4b3efed7ec | ||
|
bc28c5da8e | ||
|
5bd9c1611d | ||
|
6caca4946b | ||
|
ee78e7613f | ||
|
2df2623430 | ||
|
de5f850cdb | ||
|
ac6847045c | ||
|
df6da837dc | ||
|
41b7984a4e | ||
|
38c7e0272e | ||
|
29c690dbcd | ||
|
8ba817478b | ||
|
cacbe90102 | ||
|
cb91cd5d2f | ||
|
52dfa3fe76 | ||
|
29a1c7ac09 | ||
|
6eea51eeeb | ||
|
2149af0e74 | ||
|
142a647b7a | ||
|
6e916ddd35 | ||
|
159b00145d | ||
|
26ce16baa2 | ||
|
0622fe142b | ||
|
4805b52d42 | ||
|
962617086e | ||
|
4f6277b6b5 | ||
|
5aaab9eb8c | ||
|
ef402bb5c3 | ||
|
85ac9001d6 | ||
|
7939bffcdd | ||
|
bb58aa8e31 | ||
|
1d35149191 | ||
|
be03764029 | ||
|
a07874d468 | ||
|
90d7ae8776 | ||
|
93e0562353 | ||
|
4c5d547d9c | ||
|
9a3a64010f | ||
|
e59a6f4c9e | ||
|
1506e68587 | ||
|
671cba4f68 | ||
|
374eb8f4bf | ||
|
60f7a2b3e4 | ||
|
7744172c63 | ||
|
5a763aee8d | ||
|
c14b2c6905 | ||
|
0871376922 | ||
|
c5fe9a6dc0 | ||
|
fbbcd02384 | ||
|
d34987f9c1 | ||
|
9e0565c655 | ||
|
443081c90b | ||
|
03fc09e3c6 | ||
|
45323c2b2f | ||
|
67ee73782c | ||
|
2bb9a29ddc | ||
|
5cbd363597 | ||
|
aa6ded0ea4 | ||
|
3c61dc2b57 | ||
|
3e528ddccf | ||
|
cba65d6d08 | ||
|
8d418611a2 | ||
|
98b0f0f8ba | ||
|
6f66e6d9be | ||
|
8b06299bad | ||
|
5a99981827 | ||
|
e30ad3feb4 | ||
|
77657a9154 | ||
|
3059b1ea80 | ||
|
4037c34393 | ||
|
e671a2ad02 | ||
|
1ea091f215 | ||
|
87fa4ae3ac | ||
|
d7a1dca004 | ||
|
fe48340327 | ||
|
b4c6aa41a7 | ||
|
1696aee212 | ||
|
585379d47a | ||
|
2595b5d7d8 | ||
|
f858adc884 | ||
|
44e135ce1e | ||
|
9a9ce30b16 | ||
|
0e2b80d5d7 | ||
|
1b1ab6a66e | ||
|
0284e9d488 | ||
|
f91309c7e4 | ||
|
cd012e115b | ||
|
df9e3968dc | ||
|
c237eaa254 | ||
|
f757d7d1a5 | ||
|
4fb1366aaf | ||
|
8166e33e7f | ||
|
ff3b1c9eb2 | ||
|
4924769549 | ||
|
e4fa963bdf | ||
|
54e8bb2228 | ||
|
99e7e7876e | ||
|
62c190d841 | ||
|
84d2c02a09 | ||
|
fc0ae42450 | ||
|
9599f921a5 | ||
|
e125e9aba1 | ||
|
55a77c734d | ||
|
ccd8af09b9 | ||
|
f2d02a4187 | ||
|
f19d34a5a1 | ||
|
f1534c91e2 | ||
|
cbda060b86 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -227,8 +227,12 @@ pip-log.txt
|
||||
/cache
|
||||
/whitelist.txt
|
||||
DEBUG
|
||||
config.ini.php
|
||||
|
||||
######################
|
||||
## VisualStudioCode ##
|
||||
######################
|
||||
.vscode/*
|
||||
.vscode/*
|
||||
|
||||
#Builder
|
||||
.buildconfig
|
||||
|
17
.travis.yml
17
.travis.yml
@@ -1,11 +1,9 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: php
|
||||
php:
|
||||
- '5.6'
|
||||
- '7.0'
|
||||
- hhvm
|
||||
- nightly
|
||||
|
||||
install:
|
||||
- pear channel-update pear.php.net
|
||||
- pear install PHP_CodeSniffer
|
||||
|
||||
script:
|
||||
@@ -14,6 +12,13 @@ script:
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
include:
|
||||
- php: 5.6
|
||||
- php: 7.0
|
||||
- php: hhvm
|
||||
- php: nightly
|
||||
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
- php: nightly
|
||||
- php: nightly
|
||||
|
148
CHANGELOG.md
148
CHANGELOG.md
@@ -1,75 +1,105 @@
|
||||
rss-bridge Changelog
|
||||
===
|
||||
|
||||
RSS-Bridge 2017-08-19
|
||||
==
|
||||
|
||||
## General changes
|
||||
* whitelist: Do case-insensitive whitelist matching
|
||||
* [FeedExpander] Fix Serialization of 'SimpleXMLElement' is not allowed
|
||||
* [FeedExpander] Remove whitespace from source content
|
||||
* [index] Add GET parameter 'q' for search queries
|
||||
- **Example**: You can now add `&q=Twitter` to load into the search field
|
||||
* [index] Check permissions for cache folder and whitelist file
|
||||
* [index] Show bridge options when loading with URL fragment
|
||||
- **Example**: You can now add `#bridge-Twitter` to load the card with all
|
||||
parameters visible
|
||||
* [style] Center search cursor and hide placeholder
|
||||
* [validation] Fix error on undefined optional numeric value
|
||||
|
||||
## Modified bridges
|
||||
* [DanbooruBridge] Allow descendant classes to override tag collection
|
||||
* [DribbbleBridge] Add dribble bridge listing last dribble popular shots (#558)
|
||||
* [FacebookBridge] Fix & in URLs
|
||||
* [GelbooruBridge] Fix bridge not getting tags correctly
|
||||
* [GoComicsBridge] Fix for page structure changes (#568)
|
||||
* [LeBonCoinBridge] Fix bridge is marked executable
|
||||
* [LWNprevBridge] Fix everchanging url
|
||||
* [YoutubeBridge] Fix error on certain keywords
|
||||
* [YoutubeBridge] Fix issues loading playlists
|
||||
|
||||
## Removed bridges
|
||||
* VineBridge
|
||||
|
||||
RSS-Bridge 2017-08-03
|
||||
==
|
||||
|
||||
## Important changes
|
||||
RSS-Bridge now has [contribution guidelines](CONTRIBUTING.md)
|
||||
[phpcs rules](phpcs.xml) follow the [contribution guidelines](CONTRIBUTING.md)
|
||||
* RSS-Bridge now has [contribution guidelines](CONTRIBUTING.md)
|
||||
* [phpcs rules](phpcs.xml) follow the [contribution guidelines](CONTRIBUTING.md)
|
||||
|
||||
## General changes
|
||||
Added a search bar to make searching for bridges easier
|
||||
Added user friendly error page for when a bridge fails
|
||||
Added caching of extraInfos (name, uri)
|
||||
Added an indicator to warn for bridges using HTTP instead of HTTPS
|
||||
Various bug fixes and improvements
|
||||
* Added a search bar to make searching for bridges easier
|
||||
* Added user friendly error page for when a bridge fails
|
||||
* Added caching of extraInfos (name, uri)
|
||||
* Added an indicator to warn for bridges using HTTP instead of HTTPS
|
||||
* Various bug fixes and improvements
|
||||
|
||||
## Modified bridges
|
||||
[AllocineFRBridge] Update Faux Raccord link
|
||||
[DanbooruBridge] Fix broken URI
|
||||
[DuckDuckGoBridge] Disable DuckDuckGo redirects so that the links returned are correct.
|
||||
[FacebookBridge] Add option to hide posts with facebook videos
|
||||
[FacebookBridge] Add requester languages to HTTP header
|
||||
[FacebookBridge] Handle summary posts
|
||||
[FacebookBridge] Replace 'novideo' with 'media_type'
|
||||
[FilterBridge] Initial implementation of basic title permit and block
|
||||
[FlickrTagBridge] Fix and improve bridge by using the FlickrExploreBridge approach
|
||||
[GooglePlusPostBridge] Autofix user names
|
||||
[GooglePlusPostBridge] Fix bridge implementation
|
||||
[GooglePlusPostBridge] Fix content loading
|
||||
[InstagramBridge] Add option to filter for videos and pictures
|
||||
[LWNprevBridge] full rewrite
|
||||
[MangareaderBridge] Fix double forward slashes
|
||||
[NasaApodBridge] Use HTTPS instead of HTTP
|
||||
[PinterestBridge] Fix checkbox not working
|
||||
[PinterestBridge] Fix implementation after DOM changes
|
||||
[RTBFBridge] Update URI
|
||||
[SexactuBridge] Fix URI and timestamp
|
||||
[SexactuBridge] Use most modern version of bridge api and cached pages (#504)
|
||||
[ShanaprojectBridge] Don't throw error if timestamp is missing
|
||||
[TwitterBridge] Add option to hide retweets
|
||||
[TwitterBridge] Avoid empty content caused by new login policy
|
||||
[TwitterBridge] Fix double slashes in URI
|
||||
[TwitterBridge] Fix missing spaces
|
||||
[TwitterBridge] Fix title includes anchors in plaintext format
|
||||
[TwitterBridge] ignore promoted tweets
|
||||
[TwitterBridge] Optimize returned image sizes
|
||||
[TwitterBridge] Show quotes and pictures
|
||||
[WebfailBridge] Properly handle gifs (DOM changed)
|
||||
[YoutubeBridge] Improve readability of feed contents
|
||||
[YoutubeBridge] Improve URL handling in video descriptions
|
||||
* AllocineFRBridge] Update Faux Raccord link
|
||||
* [DanbooruBridge] Fix broken URI
|
||||
* [DuckDuckGoBridge] Disable DuckDuckGo redirects so that the links returned are correct.
|
||||
* [FacebookBridge] Add option to hide posts with facebook videos
|
||||
* [FacebookBridge] Add requester languages to HTTP header
|
||||
* [FacebookBridge] Handle summary posts
|
||||
* [FacebookBridge] Replace 'novideo' with 'media_type'
|
||||
* [FilterBridge] Initial implementation of basic title permit and block
|
||||
* [FlickrTagBridge] Fix and improve bridge by using the FlickrExploreBridge approach
|
||||
* [GooglePlusPostBridge] Autofix user names
|
||||
* [GooglePlusPostBridge] Fix bridge implementation
|
||||
* [GooglePlusPostBridge] Fix content loading
|
||||
* [InstagramBridge] Add option to filter for videos and pictures
|
||||
* [LWNprevBridge] full rewrite
|
||||
* [MangareaderBridge] Fix double forward slashes
|
||||
* [NasaApodBridge] Use HTTPS instead of HTTP
|
||||
* [PinterestBridge] Fix checkbox not working
|
||||
* [PinterestBridge] Fix implementation after DOM changes
|
||||
* [RTBFBridge] Update URI
|
||||
* [SexactuBridge] Fix URI and timestamp
|
||||
* [SexactuBridge] Use most modern version of bridge api and cached pages (#504)
|
||||
* [ShanaprojectBridge] Don't throw error if timestamp is missing
|
||||
* [TwitterBridge] Add option to hide retweets
|
||||
* [TwitterBridge] Avoid empty content caused by new login policy
|
||||
* [TwitterBridge] Fix double slashes in URI
|
||||
* [TwitterBridge] Fix missing spaces
|
||||
* [TwitterBridge] Fix title includes anchors in plaintext format
|
||||
* [TwitterBridge] ignore promoted tweets
|
||||
* [TwitterBridge] Optimize returned image sizes
|
||||
* [TwitterBridge] Show quotes and pictures
|
||||
* [WebfailBridge] Properly handle gifs (DOM changed)
|
||||
* [YoutubeBridge] Improve readability of feed contents
|
||||
* [YoutubeBridge] Improve URL handling in video descriptions
|
||||
|
||||
## New bridges
|
||||
AmazonBridge
|
||||
DiceBridge
|
||||
EtsyBridge
|
||||
FB2Bridge
|
||||
FilterBridge
|
||||
FlickrBridge
|
||||
GithubSearchBridge
|
||||
GoComicsBridge
|
||||
KATBridge
|
||||
KernelBugTrackerBridge
|
||||
MixCloudBridge
|
||||
MoinMoinBridge
|
||||
RainbowSixSiegeBridge
|
||||
SteamBridge
|
||||
TheTVDBBridge
|
||||
Torrent9Bridge
|
||||
UsbekEtRicaBridge
|
||||
WikiLeaksBridge
|
||||
WordPressPluginUpdateBridge
|
||||
* AmazonBridge
|
||||
* DiceBridge
|
||||
* EtsyBridge
|
||||
* FB2Bridge
|
||||
* FilterBridge
|
||||
* FlickrBridge
|
||||
* GithubSearchBridge
|
||||
* GoComicsBridge
|
||||
* KATBridge
|
||||
* KernelBugTrackerBridge
|
||||
* MixCloudBridge
|
||||
* MoinMoinBridge
|
||||
* RainbowSixSiegeBridge
|
||||
* SteamBridge
|
||||
* TheTVDBBridge
|
||||
* Torrent9Bridge
|
||||
* UsbekEtRicaBridge
|
||||
* WikiLeaksBridge
|
||||
* WordPressPluginUpdateBridge
|
||||
|
||||
Alpha 0.2
|
||||
===
|
||||
|
44
README.md
44
README.md
@@ -7,23 +7,23 @@ rss-bridge is a PHP project capable of generating ATOM feeds for websites which
|
||||
Supported sites/pages (main)
|
||||
===
|
||||
|
||||
* `FlickrExplore` : [Latest interesting images](http://www.flickr.com/explore) from Flickr
|
||||
* `GoogleSearch` : Most recent results from Google Search
|
||||
* `GooglePlus` : Most recent posts of user timeline
|
||||
* `Twitter` : Return keyword/hashtag search or user timeline
|
||||
* `Identi.ca` : Identica user timeline (Should be compatible with other Pump.io instances)
|
||||
* `YouTube` : YouTube user channel, playlist or search
|
||||
* `Cryptome` : Returns the most recent documents from [Cryptome.org](http://cryptome.org/)
|
||||
* `DansTonChat`: Most recent quotes from [danstonchat.com](http://danstonchat.com/)
|
||||
* `DuckDuckGo`: Most recent results from [DuckDuckGo.com](https://duckduckgo.com/)
|
||||
* `Instagram`: Most recent photos from an Instagram user
|
||||
* `OpenClassrooms`: Lastest tutorials from [fr.openclassrooms.com](http://fr.openclassrooms.com/)
|
||||
* `Pinterest`: Most recent photos from user or search
|
||||
* `ScmbBridge`: Newest stories from [secouchermoinsbete.fr](http://secouchermoinsbete.fr/)
|
||||
* `Wikipedia`: highlighted articles from [Wikipedia](https://wikipedia.org/) in English, German, French or Esperanto
|
||||
* `Bandcamp` : Returns last release from [bandcamp](https://bandcamp.com/) for a tag
|
||||
* `ThePirateBay` : Returns the newest indexed torrents from [The Pirate Bay](https://thepiratebay.se/) with keywords
|
||||
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/)
|
||||
* `Bandcamp` : Returns last release from [bandcamp](https://bandcamp.com/) for a tag
|
||||
* `Cryptome` : Returns the most recent documents from [Cryptome.org](http://cryptome.org/)
|
||||
* `DansTonChat`: Most recent quotes from [danstonchat.com](http://danstonchat.com/)
|
||||
* `DuckDuckGo`: Most recent results from [DuckDuckGo.com](https://duckduckgo.com/)
|
||||
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/)
|
||||
* `FlickrExplore` : [Latest interesting images](http://www.flickr.com/explore) from Flickr
|
||||
* `GooglePlus` : Most recent posts of user timeline
|
||||
* `GoogleSearch` : Most recent results from Google Search
|
||||
* `Identi.ca` : Identica user timeline (Should be compatible with other Pump.io instances)
|
||||
* `Instagram`: Most recent photos from an Instagram user
|
||||
* `OpenClassrooms`: Lastest tutorials from [fr.openclassrooms.com](http://fr.openclassrooms.com/)
|
||||
* `Pinterest`: Most recent photos from user or search
|
||||
* `ScmbBridge`: Newest stories from [secouchermoinsbete.fr](http://secouchermoinsbete.fr/)
|
||||
* `ThePirateBay` : Returns the newest indexed torrents from [The Pirate Bay](https://thepiratebay.se/) with keywords
|
||||
* `Twitter` : Return keyword/hashtag search or user timeline
|
||||
* `Wikipedia`: highlighted articles from [Wikipedia](https://wikipedia.org/) in English, German, French or Esperanto
|
||||
* `YouTube` : YouTube user channel, playlist or search
|
||||
|
||||
Plus [many other bridges](bridges/) to enable, thanks to the community
|
||||
|
||||
@@ -31,11 +31,11 @@ Output format
|
||||
===
|
||||
Output format can take several forms:
|
||||
|
||||
* `Atom` : ATOM Feed, for use in RSS/Feed readers
|
||||
* `Mrss` : MRSS Feed, for use in RSS/Feed readers
|
||||
* `Json` : Json, for consumption by other applications.
|
||||
* `Html` : Simple html page.
|
||||
* `Plaintext` : raw text (php object, as returned by print_r)
|
||||
* `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)
|
||||
|
||||
Screenshot
|
||||
===
|
||||
|
@@ -26,7 +26,7 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
|
||||
switch($this->getInput('category')) {
|
||||
case 'faux-raccord':
|
||||
$uri = static::URI . 'video/programme-12284/saison-29841/';
|
||||
$uri = static::URI . 'video/programme-12284/saison-32180/';
|
||||
break;
|
||||
case 'top-5':
|
||||
$uri = static::URI . 'video/programme-12299/saison-29561/';
|
||||
@@ -64,7 +64,7 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
self::PARAMETERS[$this->queriedContext]['category']['values']
|
||||
);
|
||||
|
||||
foreach($html->find('figure.media-meta-fig') as $element) {
|
||||
foreach($html->find('.media-meta-list figure.media-meta-fig') as $element) {
|
||||
$item = array();
|
||||
|
||||
$title = $element->find('div.titlebar h3.title a', 0);
|
||||
|
@@ -3,24 +3,28 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'Arte +7';
|
||||
const URI = 'http://www.arte.tv/';
|
||||
const URI = 'https://www.arte.tv/';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Returns newest videos from ARTE +7';
|
||||
|
||||
const API_TOKEN = 'Nzc1Yjc1ZjJkYjk1NWFhN2I2MWEwMmRlMzAzNjI5NmU3NWU3ODg4ODJjOWMxNTMxYzEzZGRjYjg2ZGE4MmIwOA';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'Catégorie (Français)' => array(
|
||||
'catfr' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Catégorie',
|
||||
'values' => array(
|
||||
'Toutes les vidéos (français)' => 'toutes-les-videos',
|
||||
'Actu & société' => 'actu-société',
|
||||
'Séries & fiction' => 'séries-fiction',
|
||||
'Cinéma' => 'cinéma',
|
||||
'Arts & spectacles classiques' => 'arts-spectacles-classiques',
|
||||
'Culture pop' => 'culture-pop',
|
||||
'Découverte' => 'découverte',
|
||||
'Histoire' => 'histoire',
|
||||
'Junior' => 'junior'
|
||||
'Toutes les vidéos (français)' => null,
|
||||
'Actu & société' => 'ACT',
|
||||
'Séries & fiction' => 'SER',
|
||||
'Cinéma' => 'CIN',
|
||||
'Arts & spectacles classiques' => 'ARS',
|
||||
'Culture pop' => 'CPO',
|
||||
'Découverte' => 'DEC',
|
||||
'Histoire' => 'HIST',
|
||||
'Science' => 'SCI',
|
||||
'Autre' => 'AUT'
|
||||
)
|
||||
)
|
||||
),
|
||||
@@ -29,15 +33,16 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
'type' => 'list',
|
||||
'name' => 'Catégorie',
|
||||
'values' => array(
|
||||
'Alle Videos (deutsch)' => 'alle-videos',
|
||||
'Aktuelles & Gesellschaft' => 'aktuelles-gesellschaft',
|
||||
'Fernsehfilme & Serien' => 'fernsehfilme-serien',
|
||||
'Kino' => 'kino',
|
||||
'Kunst & Kultur' => 'kunst-kultur',
|
||||
'Popkultur & Alternativ' => 'popkultur-alternativ',
|
||||
'Entdeckung' => 'entdeckung',
|
||||
'Geschichte' => 'geschichte',
|
||||
'Junior' => 'junior'
|
||||
'Alle Videos (deutsch)' => null,
|
||||
'Aktuelles & Gesellschaft' => 'ACT',
|
||||
'Fernsehfilme & Serien' => 'SER',
|
||||
'Kino' => 'CIN',
|
||||
'Kunst & Kultur' => 'ARS',
|
||||
'Popkultur & Alternativ' => 'CPO',
|
||||
'Entdeckung' => 'DEC',
|
||||
'Geschichte' => 'HIST',
|
||||
'Wissenschaft' => 'SCI',
|
||||
'Sonstiges' => 'AUT'
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -55,44 +60,37 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
break;
|
||||
}
|
||||
|
||||
$url = self::URI . 'guide/' . $lang . '/plus7/' . $category;
|
||||
$input = getContents($url) or die('Could not request ARTE.');
|
||||
$url = 'https://api.arte.tv/api/opa/v3/videos?sort=-lastModified&limit=10&language='
|
||||
. $lang
|
||||
. ($category != null ? '&category.code=' . $category : '');
|
||||
|
||||
if(strpos($input, 'categoryVideoSet') !== false) {
|
||||
$input = explode('categoryVideoSet="', $input);
|
||||
$input = explode('}}', $input[1]);
|
||||
$input = $input[0] . '}}';
|
||||
} else {
|
||||
$input = explode('videoSet="', $input);
|
||||
$input = explode('}]}', $input[1]);
|
||||
$input = $input[0] . '}]}';
|
||||
}
|
||||
$header = array(
|
||||
'Authorization: Bearer ' . self::API_TOKEN
|
||||
);
|
||||
|
||||
$input_json = json_decode(html_entity_decode($input, ENT_QUOTES), true);
|
||||
$input = getContents($url, $header) or die('Could not request ARTE.');
|
||||
$input_json = json_decode($input, true);
|
||||
|
||||
foreach($input_json['videos'] as $element) {
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = str_replace("autoplay=1", "", $element['url']);
|
||||
$item['uri'] = $element['url'];
|
||||
$item['id'] = $element['id'];
|
||||
|
||||
$hack_broadcast_time = $element['rights_end'];
|
||||
$hack_broadcast_time = strtok($hack_broadcast_time, 'T');
|
||||
$hack_broadcast_time = strtok('T');
|
||||
|
||||
$item['timestamp'] = strtotime($element['scheduled_on'] . 'T' . $hack_broadcast_time);
|
||||
$item['timestamp'] = strtotime($element['videoRightsBegin']);
|
||||
$item['title'] = $element['title'];
|
||||
|
||||
if(!empty($element['subtitle']))
|
||||
$item['title'] = $element['title'] . ' | ' . $element['subtitle'];
|
||||
|
||||
$item['duration'] = round((int)$element['duration'] / 60);
|
||||
$item['content'] = $element['teaser']
|
||||
$item['duration'] = round((int)$element['durationSeconds'] / 60);
|
||||
$item['content'] = $element['teaserText']
|
||||
. '<br><br>'
|
||||
. $item['duration']
|
||||
. 'min<br><a href="'
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. $element['thumbnail_url']
|
||||
. $element['mainImage']['url']
|
||||
. '" /></a>';
|
||||
|
||||
$this->items[] = $item;
|
||||
|
65
bridges/BloombergBridge.php
Normal file
65
bridges/BloombergBridge.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
class BloombergBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Bloomberg';
|
||||
const URI = 'https://www.bloomberg.com/';
|
||||
const DESCRIPTION = 'Trending stories from Bloomberg';
|
||||
const MAINTAINER = 'mdemoss';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'Trending Stories' => array(),
|
||||
'From Search' => array(
|
||||
'q' => array(
|
||||
'name' => 'Keyword',
|
||||
'required' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getName()
|
||||
{
|
||||
switch($this->queriedContext) {
|
||||
case 'Trending Stories':
|
||||
return self::NAME . ' Trending Stories';
|
||||
case 'From Search':
|
||||
if (!is_null($this->getInput('q'))) {
|
||||
return self::NAME . ' Search : ' . $this->getInput('q');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
switch($this->queriedContext) {
|
||||
case 'Trending Stories': // Get list of top new <article>s from the front page.
|
||||
$html = getSimpleHTMLDOMCached($this->getURI(), 300);
|
||||
$stories = $html->find('ul.top-news-v3__stories article.top-news-v3-story');
|
||||
break;
|
||||
case 'From Search': // Get list of <article> elements from search.
|
||||
$html = getSimpleHTMLDOMCached(
|
||||
$this->getURI() .
|
||||
'search?sort=time:desc&page=1&query=' .
|
||||
urlencode($this->getInput('q')), 300
|
||||
);
|
||||
$stories = $html->find('div.search-result-items article.search-result-story');
|
||||
break;
|
||||
}
|
||||
foreach ($stories as $element) {
|
||||
$item['uri'] = $element->find('h1 a', 0)->href;
|
||||
if (preg_match('#^https://#i', $item['uri']) !== 1) {
|
||||
$item['uri'] = $this->getURI() . $item['uri'];
|
||||
}
|
||||
$articleHtml = getSimpleHTMLDOMCached($item['uri']);
|
||||
if (!$articleHtml) {
|
||||
continue;
|
||||
}
|
||||
$item['title'] = $element->find('h1 a', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($articleHtml->find('meta[name=iso-8601-publish-date],meta[name=date]', 0)->content);
|
||||
$item['content'] = $articleHtml->find('meta[name=description]', 0)->content;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
25
bridges/ChristianDailyReporterBridge.php
Normal file
25
bridges/ChristianDailyReporterBridge.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
class ChristianDailyReporterBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'rogerdc';
|
||||
const NAME = 'Christian Daily Reporter Unofficial RSS';
|
||||
const URI = 'https://www.christiandailyreporter.com/';
|
||||
const DESCRIPTION = 'The Unofficial Christian Daily Reporter RSS';
|
||||
// const CACHE_TIMEOUT = 86400; // 1 day
|
||||
|
||||
|
||||
public function collectData() {
|
||||
$uri = 'https://www.christiandailyreporter.com/';
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not request Christian Daily Reporter.');
|
||||
foreach($html->find('div.top p a,div.column p a') as $element) {
|
||||
$item = array();
|
||||
// Title
|
||||
$item['title'] = $element->innertext;
|
||||
// URL
|
||||
$item['uri'] = $element->href;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -23,6 +23,7 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
|
||||
const PATHTODATA = 'article';
|
||||
const IDATTRIBUTE = 'data-id';
|
||||
const TAGATTRIBUTE = 'alt';
|
||||
|
||||
protected function getFullURI(){
|
||||
return $this->getURI()
|
||||
@@ -30,6 +31,10 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
. '&tags=' . urlencode($this->getInput('t'));
|
||||
}
|
||||
|
||||
protected function getTags($element){
|
||||
return $element->find('img', 0)->getAttribute(static::TAGATTRIBUTE);
|
||||
}
|
||||
|
||||
protected function getItemFromElement($element){
|
||||
// Fix links
|
||||
defaultLinkTo($element, $this->getURI());
|
||||
@@ -39,7 +44,7 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
$item['postid'] = (int)preg_replace("/[^0-9]/", '', $element->getAttribute(static::IDATTRIBUTE));
|
||||
$item['timestamp'] = time();
|
||||
$thumbnailUri = $element->find('img', 0)->src;
|
||||
$item['tags'] = $element->find('img', 0)->getAttribute('alt');
|
||||
$item['tags'] = $this->getTags($element);
|
||||
$item['title'] = $this->getName() . ' | ' . $item['postid'];
|
||||
$item['content'] = '<a href="'
|
||||
. $item['uri']
|
||||
|
@@ -15,8 +15,13 @@ class DansTonChatBridge extends BridgeAbstract {
|
||||
foreach($html->find('div.item') as $element) {
|
||||
$item = array();
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
$item['title'] = 'DansTonChat ' . $element->find('a', 1)->plaintext;
|
||||
$item['content'] = $element->find('a', 0)->innertext;
|
||||
$titleContent = $element->find('h3 a', 0);
|
||||
if($titleContent) {
|
||||
$item['title'] = 'DansTonChat ' . html_entity_decode($titleContent->plaintext, ENT_QUOTES);
|
||||
} else {
|
||||
$item['title'] = 'DansTonChat';
|
||||
}
|
||||
$item['content'] = $element->find('div.item-content a', 0)->innertext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
478
bridges/DealabsBridge.php
Normal file
478
bridges/DealabsBridge.php
Normal file
@@ -0,0 +1,478 @@
|
||||
<?php
|
||||
class DealabsBridge extends BridgeAbstract {
|
||||
const NAME = 'Dealabs search bridge';
|
||||
const URI = 'https://www.dealabs.com/';
|
||||
const DESCRIPTION = 'Return the Dealabs search result using keywords';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
const PARAMETERS = array(
|
||||
'Recherche par Mot(s) clé(s)' => array (
|
||||
'q' => array(
|
||||
'name' => 'Mot(s) clé(s)',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
),
|
||||
'hide_expired' => array(
|
||||
'name' => 'Masquer les éléments expirés',
|
||||
'type' => 'checkbox',
|
||||
'required' => 'true'
|
||||
),
|
||||
'hide_local' => array(
|
||||
'name' => 'Masquer les deals locaux',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Masquer les deals en magasins physiques',
|
||||
'required' => 'true'
|
||||
),
|
||||
'priceFrom' => array(
|
||||
'name' => 'Prix minimum',
|
||||
'type' => 'text',
|
||||
'title' => 'Prix mnimum en euros',
|
||||
'required' => 'false',
|
||||
'defaultValue' => ''
|
||||
),
|
||||
'priceTo' => array(
|
||||
'name' => 'Prix maximum',
|
||||
'type' => 'text',
|
||||
'title' => 'Prix maximum en euros',
|
||||
'required' => 'false',
|
||||
'defaultValue' => ''
|
||||
),
|
||||
),
|
||||
|
||||
'Deals par groupe' => array(
|
||||
'groupe' => array(
|
||||
'name' => 'Groupe',
|
||||
'type' => 'list',
|
||||
'required' => 'true',
|
||||
'title' => 'Groupe dont il faut afficher les deals',
|
||||
'values' => array(
|
||||
'Accessoires & gadgets' => 'accessoires-gadgets',
|
||||
'Alimentation & boissons' => 'alimentation-boissons',
|
||||
'Animaux' => 'animaux',
|
||||
'Applis & logiciels' => 'applis-logiciels',
|
||||
'Consoles & jeux vidéo' => 'consoles-jeux-video',
|
||||
'Culture & divertissement' => 'culture-divertissement',
|
||||
'Gratuit' => 'gratuit',
|
||||
'Image, son & vidéo' => 'image-son-video',
|
||||
'Informatique' => 'informatique',
|
||||
'Jeux & jouets' => 'jeux-jouets',
|
||||
'Maison & jardin' => 'maison-jardin',
|
||||
'Mode & accessoires' => 'mode-accessoires',
|
||||
'Santé & cosmétiques' => 'hygiene-sante-cosmetiques',
|
||||
'Services divers' => 'services-divers',
|
||||
'Sports & plein air' => 'sports-plein-air',
|
||||
'Téléphonie' => 'telephonie',
|
||||
'Voyages & sorties' => 'voyages-sorties-restaurants'
|
||||
)
|
||||
),
|
||||
'ordre' => array(
|
||||
'name' => 'Trier par',
|
||||
'type' => 'list',
|
||||
'required' => 'true',
|
||||
'title' => 'Ordre de tri des deals',
|
||||
'values' => array(
|
||||
'Du deal le plus Hot au moins Hot' => '',
|
||||
'Du deal le plus récent au plus ancien' => '-nouveaux',
|
||||
'Du deal le plus commentés au moins commentés' => '-commentes'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
public function collectData(){
|
||||
switch($this->queriedContext) {
|
||||
case 'Recherche par Mot(s) clé(s)':
|
||||
return $this->collectDataMotsCles();
|
||||
break;
|
||||
case 'Deals par groupe':
|
||||
return $this->collectDataGroupe();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Deal data from the choosen groupe in the choose order
|
||||
*/
|
||||
public function collectDataGroupe()
|
||||
{
|
||||
|
||||
$groupe = $this->getInput('groupe');
|
||||
$ordre = $this->getInput('ordre');
|
||||
|
||||
$url = self::URI
|
||||
. '/groupe/' . $groupe . $ordre;
|
||||
$this->collectDeals($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Deal data from the choosen keywords and parameters
|
||||
*/
|
||||
public function collectDataMotsCles()
|
||||
{
|
||||
$q = $this->getInput('q');
|
||||
$hide_expired = $this->getInput('hide_expired');
|
||||
$hide_local = $this->getInput('hide_local');
|
||||
$priceFrom = $this->getInput('priceFrom');
|
||||
$priceTo = $this->getInput('priceFrom');
|
||||
|
||||
/* Even if the original website uses POST with the search page, GET works too */
|
||||
$url = self::URI
|
||||
. '/search/advanced?q='
|
||||
. urlencode($q)
|
||||
. '&hide_expired='. $hide_expired
|
||||
. '&hide_local='. $hide_local
|
||||
. '&priceFrom='. $priceFrom
|
||||
. '&priceTo='. $priceTo
|
||||
/* Some default parameters
|
||||
* search_fields : Search in Titres & Descriptions & Codes
|
||||
* sort_by : Sort the search by new deals
|
||||
* time_frame : Search will not be on a limited timeframe
|
||||
*/
|
||||
. '&search_fields[]=1&search_fields[]=2&search_fields[]=3&sort_by=new&time_frame=0';
|
||||
$this->collectDeals($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Deal data using the given URL
|
||||
*/
|
||||
public function collectDeals($url){
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request Dealabs.');
|
||||
$list = $html->find('article');
|
||||
|
||||
// Deal Image Link CSS Selector
|
||||
$selectorImageLink = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'cept-thread-image-link',
|
||||
'imgFrame',
|
||||
'imgFrame--noBorder',
|
||||
'thread-listImgCell',
|
||||
)
|
||||
);
|
||||
|
||||
// Deal Link CSS Selector
|
||||
$selectorLink = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'cept-tt',
|
||||
'thread-link',
|
||||
'linkPlain',
|
||||
)
|
||||
);
|
||||
|
||||
// Deal Hotness CSS Selector
|
||||
$selectorHot = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'flex',
|
||||
'flex--align-c',
|
||||
'flex--justify-space-between',
|
||||
'space--b-2',
|
||||
)
|
||||
);
|
||||
|
||||
// Deal Description CSS Selector
|
||||
$selectorDescription = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'cept-description-container',
|
||||
'userHtml',
|
||||
'overflow--wrap-break',
|
||||
'size--all-s',
|
||||
'size--fromW3-m',
|
||||
)
|
||||
);
|
||||
|
||||
// Deal Date CSS Selector
|
||||
$selectorDate = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'size--all-s',
|
||||
'flex',
|
||||
'flex--wrap',
|
||||
'flex--justify-e',
|
||||
'flex--grow-1',
|
||||
)
|
||||
);
|
||||
|
||||
// If there is no results, we don't parse the content because it display some random deals
|
||||
$noresult = $html->find('h3[class=size--all-l size--fromW2-xl size--fromW3-xxl]', 0);
|
||||
if($noresult != null && $noresult->plaintext == 'Il n'y a rien à afficher pour le moment :(') {
|
||||
$this->items = array();
|
||||
} else {
|
||||
foreach($list as $deal) {
|
||||
$item = array();
|
||||
$item['uri'] = $deal->find('div[class=threadGrid-title]', 0)->find('a', 0)->href;
|
||||
$item['title'] = $deal->find('a[class*='. $selectorLink .']', 0
|
||||
)->plaintext;
|
||||
$item['author'] = $deal->find('span.thread-username', 0)->plaintext;
|
||||
$item['content'] = '<table><tr><td><a href="'
|
||||
. $deal->find(
|
||||
'a[class*='. $selectorImageLink .']', 0)->href
|
||||
. '"><img src="'
|
||||
. $this->getImage($deal)
|
||||
. '"/></td><td><h2><a href="'
|
||||
. $deal->find('a[class*='. $selectorLink .']', 0)->href
|
||||
. '">'
|
||||
. $deal->find('a[class*='. $selectorLink .']', 0)->innertext
|
||||
. '</a></h2>'
|
||||
. $this->getPrix($deal)
|
||||
. $this->getReduction($deal)
|
||||
. $this->getExpedition($deal)
|
||||
. $this->getLivraison($deal)
|
||||
. $this->getOrigine($deal)
|
||||
. $deal->find('div[class='. $selectorDescription .']', 0)->innertext
|
||||
. '</td><td>'
|
||||
. $deal->find('div[class='. $selectorHot .']', 0)->children(0)->outertext
|
||||
. '</td></table>';
|
||||
$dealDateDiv = $deal->find('div[class='. $selectorDate .']', 0)
|
||||
->find('span[class=hide--toW3]');
|
||||
$itemDate = end($dealDateDiv)->plaintext;
|
||||
if(substr( $itemDate, 0, 6 ) === 'il y a') {
|
||||
$item['timestamp'] = $this->relativeDateToTimestamp($itemDate);
|
||||
} else {
|
||||
$item['timestamp'] = $this->parseDate($itemDate);
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Price from a Deal if it exists
|
||||
* @return string String of the deal price
|
||||
*/
|
||||
private function getPrix($deal)
|
||||
{
|
||||
if($deal->find(
|
||||
'span[class*=thread-price]', 0) != null) {
|
||||
return '<div>Prix : '
|
||||
. $deal->find(
|
||||
'span[class*=thread-price]', 0
|
||||
)->plaintext
|
||||
. '</div>';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Shipping costs from a Deal if it exists
|
||||
* @return string String of the deal shipping Cost
|
||||
*/
|
||||
private function getLivraison($deal)
|
||||
{
|
||||
if($deal->find('span[class*=cept-shipping-price]', 0) != null) {
|
||||
if($deal->find('span[class*=cept-shipping-price]', 0)->children(0) != null) {
|
||||
return '<div>Livraison : '
|
||||
. $deal->find('span[class*=cept-shipping-price]', 0)->children(0)->innertext
|
||||
. '</div>';
|
||||
} else {
|
||||
return '<div>Livraison : '
|
||||
. $deal->find('span[class*=cept-shipping-price]', 0)->innertext
|
||||
. '</div>';
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source of a Deal if it exists
|
||||
* @return string String of the deal source
|
||||
*/
|
||||
private function getOrigine($deal)
|
||||
{
|
||||
if($deal->find('a[class=text--color-greyShade]', 0) != null) {
|
||||
return '<div>Origine : '
|
||||
. $deal->find('a[class=text--color-greyShade]', 0)->outertext
|
||||
. '</div>';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original Price and discout from a Deal if it exists
|
||||
* @return string String of the deal original price and discount
|
||||
*/
|
||||
private function getReduction($deal)
|
||||
{
|
||||
if($deal->find('span[class*=mute--text text--lineThrough]', 0) != null) {
|
||||
$discountHtml = $deal->find('span[class=space--ml-1 size--all-l size--fromW3-xl]', 0);
|
||||
if($discountHtml != null) {
|
||||
$discount = $discountHtml->plaintext;
|
||||
} else {
|
||||
$discount = '';
|
||||
}
|
||||
return '<div>Réduction : <span style="text-decoration: line-through;">'
|
||||
. $deal->find(
|
||||
'span[class*=mute--text text--lineThrough]', 0
|
||||
)->plaintext
|
||||
. '</span> '
|
||||
. $discount
|
||||
. '</div>';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Picture URL from a Deal if it exists
|
||||
* @return string String of the deal Picture URL
|
||||
*/
|
||||
private function getImage($deal)
|
||||
{
|
||||
|
||||
$selectorLazy = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'thread-image',
|
||||
'width--all-auto',
|
||||
'height--all-auto',
|
||||
'imgFrame-img',
|
||||
'cept-thread-img',
|
||||
'img--dummy',
|
||||
'js-lazy-img'
|
||||
)
|
||||
);
|
||||
|
||||
$selectorPlain = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'thread-image',
|
||||
'width--all-auto',
|
||||
'height--all-auto',
|
||||
'imgFrame-img',
|
||||
'cept-thread-img'
|
||||
)
|
||||
);
|
||||
if($deal->find('img[class='. $selectorLazy .']', 0) != null) {
|
||||
return json_decode(
|
||||
html_entity_decode(
|
||||
$deal->find('img[class='. $selectorLazy .']', 0)
|
||||
->getAttribute('data-lazy-img')))->{'src'};
|
||||
} else {
|
||||
return $deal->find('img[class='. $selectorPlain .']', 0 )->src;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the originating country from a Deal if it existsa
|
||||
* @return string String of the deal originating country
|
||||
*/
|
||||
private function getExpedition($deal)
|
||||
{
|
||||
$selector = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'meta-ribbon',
|
||||
'overflow--wrap-off',
|
||||
'space--l-3',
|
||||
'text--color-greyShade'
|
||||
)
|
||||
);
|
||||
if($deal->find('span[class='. $selector .']', 0) != null) {
|
||||
return '<div>'
|
||||
. $deal->find('span[class='. $selector .']', 0)->children(2)->plaintext
|
||||
. '</div>';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a French date into a timestam
|
||||
* @return int timestamp of the input date
|
||||
*/
|
||||
private function parseDate($string)
|
||||
{
|
||||
$month_fr = array(
|
||||
'janvier',
|
||||
'février',
|
||||
'mars',
|
||||
'avril',
|
||||
'mai',
|
||||
'juin',
|
||||
'juillet',
|
||||
'août',
|
||||
'septembre',
|
||||
'octobre',
|
||||
'novembre',
|
||||
'décembre'
|
||||
);
|
||||
$month_en = array(
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
);
|
||||
$string = str_replace('Actualisé ', '', $string);
|
||||
$date_str = trim(str_replace($month_fr, $month_en, $string));
|
||||
|
||||
if(!preg_match('/[0-9]{4}/', $string)) {
|
||||
$date_str .= ' ' . date('Y');
|
||||
}
|
||||
$date_str .= ' 00:00';
|
||||
|
||||
$date = DateTime::createFromFormat('j F Y H:i', $date_str);
|
||||
return $date->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a relate French date into a timestam
|
||||
* @return int timestamp of the input date
|
||||
*/
|
||||
private function relativeDateToTimestamp($str) {
|
||||
$date = new DateTime();
|
||||
$search = array(
|
||||
'il y a ',
|
||||
'min',
|
||||
'h',
|
||||
'jour',
|
||||
'jours',
|
||||
'mois',
|
||||
'ans',
|
||||
'et '
|
||||
);
|
||||
$replace = array(
|
||||
'-',
|
||||
'minute',
|
||||
'hour',
|
||||
'day',
|
||||
'month',
|
||||
'year',
|
||||
''
|
||||
);
|
||||
|
||||
$date->modify(str_replace($search, $replace, $str));
|
||||
return $date->getTimestamp();
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
switch($this->queriedContext) {
|
||||
case 'Recherche par Mot(s) clé(s)':
|
||||
return self::NAME . ' - Recherche : '. $this->getInput('q');
|
||||
break;
|
||||
case 'Deals par groupe':
|
||||
$values = self::PARAMETERS['Deals par groupe']['groupe']['values'];
|
||||
$groupe = array_search($this->getInput('groupe'), $values);
|
||||
return self::NAME . ' - Groupe : '. $groupe;
|
||||
break;
|
||||
default: // Return default value
|
||||
return self::NAME;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
166
bridges/DemonoidBridge.php
Normal file
166
bridges/DemonoidBridge.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
class DemonoidBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'metaMMA';
|
||||
const NAME = 'Demonoid';
|
||||
const URI = 'https://www.demonoid.pw/';
|
||||
const DESCRIPTION = 'Returns results from search';
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
'q' => array(
|
||||
'name' => 'keywords',
|
||||
'exampleValue' => 'keyword1 keyword2…',
|
||||
'required' => true,
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All' => 0,
|
||||
'Movies' => 1,
|
||||
'Music' => 2,
|
||||
'TV' => 3,
|
||||
'Games' => 4,
|
||||
'Applications' => 5,
|
||||
'Pictures' => 8,
|
||||
'Anime' => 9,
|
||||
'Comics' => 10,
|
||||
'Books' => 11,
|
||||
'Audiobooks' => 17
|
||||
)
|
||||
)
|
||||
), array(
|
||||
'catOnly' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All' => 0,
|
||||
'Movies' => 1,
|
||||
'Music' => 2,
|
||||
'TV' => 3,
|
||||
'Games' => 4,
|
||||
'Applications' => 5,
|
||||
'Pictures' => 8,
|
||||
'Anime' => 9,
|
||||
'Comics' => 10,
|
||||
'Books' => 11,
|
||||
'Audiobooks' => 17
|
||||
)
|
||||
)
|
||||
), array(
|
||||
'userid' => array(
|
||||
'name' => 'user id',
|
||||
'exampleValue' => '00000',
|
||||
'required' => true,
|
||||
'type' => 'number'
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All' => 0,
|
||||
'Movies' => 1,
|
||||
'Music' => 2,
|
||||
'TV' => 3,
|
||||
'Games' => 4,
|
||||
'Applications' => 5,
|
||||
'Pictures' => 8,
|
||||
'Anime' => 9,
|
||||
'Comics' => 10,
|
||||
'Books' => 11,
|
||||
'Audiobooks' => 17
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
if(!empty($this->getInput('q'))) {
|
||||
|
||||
$html = getSimpleHTMLDOM(
|
||||
self::URI .
|
||||
'files/?category=' .
|
||||
rawurlencode($this->getInput('category')) .
|
||||
'&subcategory=All&quality=All&seeded=2&external=2&query=' .
|
||||
urlencode($this->getInput('q')) .
|
||||
'&uid=0&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
|
||||
} elseif(!empty($this->getInput('catOnly'))) {
|
||||
|
||||
$html = getSimpleHTMLDOM(
|
||||
self::URI .
|
||||
'files/?uid=0&category=' .
|
||||
rawurlencode($this->getInput('catOnly')) .
|
||||
'&subcategory=0&language=0&seeded=2&quality=0&query=&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
|
||||
} elseif(!empty($this->getInput('userid'))) {
|
||||
|
||||
$html = getSimpleHTMLDOM(
|
||||
self::URI .
|
||||
'files/?uid=' .
|
||||
rawurlencode($this->getInput('userid')) .
|
||||
'&seeded=2'
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
|
||||
} else {
|
||||
returnServerError('Invalid parameters !');
|
||||
}
|
||||
|
||||
if(preg_match('~No torrents found~', $html)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $html->find('td[class=ctable_content_no_pad]', 0);
|
||||
$cursorCount = 4;
|
||||
$elementCount = 0;
|
||||
while($elementCount != 40) {
|
||||
$elementCount++;
|
||||
$currentElement = $table->find('tr', $cursorCount);
|
||||
if(preg_match('~items total~', $currentElement)) {
|
||||
break;
|
||||
}
|
||||
$item = array();
|
||||
//Do we have a date ?
|
||||
if(preg_match('~Added.*?(.*)~', $currentElement->plaintext, $dateStr)) {
|
||||
if(preg_match('~today~', $dateStr[0])) {
|
||||
date_default_timezone_set('UTC');
|
||||
$timestamp = mktime(0, 0, 0, gmdate('n'), gmdate('j'), gmdate('Y'));
|
||||
} else {
|
||||
preg_match('~(?<=ed on ).*\d+~', $currentElement->plaintext, $fullDateStr);
|
||||
date_default_timezone_set('UTC');
|
||||
$dateObj = strptime($fullDateStr[0], '%A, %b %d, %Y');
|
||||
$timestamp = mktime(0, 0, 0, $dateObj['tm_mon'] + 1, $dateObj['tm_mday'], 1900 + $dateObj['tm_year']);
|
||||
}
|
||||
$cursorCount++;
|
||||
}
|
||||
|
||||
$content = $table->find('tr', $cursorCount)->find('a', 1);
|
||||
$cursorCount++;
|
||||
$torrentInfo = $table->find('tr', $cursorCount);
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['title'] = $content->plaintext;
|
||||
$item['id'] = self::URI . $content->href;
|
||||
$item['uri'] = self::URI . $content->href;
|
||||
$item['author'] = $torrentInfo->find('a[class=user]', 0)->plaintext;
|
||||
$item['seeders'] = $torrentInfo->find('font[class=green]', 0)->plaintext;
|
||||
$item['leechers'] = $torrentInfo->find('font[class=red]', 0)->plaintext;
|
||||
$item['size'] = $torrentInfo->find('td', 3)->plaintext;
|
||||
$item['content'] = 'Uploaded by ' . $item['author']
|
||||
. ' , Size ' . $item['size']
|
||||
. '<br>seeders: '
|
||||
. $item['seeders']
|
||||
. ' | leechers: '
|
||||
. $item['leechers']
|
||||
. '<br><a href="'
|
||||
. $item['id']
|
||||
. '">info page</a>';
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
$cursorCount++;
|
||||
}
|
||||
}
|
||||
}
|
112
bridges/DiscogsBridge.php
Normal file
112
bridges/DiscogsBridge.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
class DiscogsBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'DiscogsBridge';
|
||||
const URI = 'https://www.discogs.com/';
|
||||
const DESCRIPTION = 'Returns releases from discogs';
|
||||
const PARAMETERS = array(
|
||||
'Artist Releases' => array(
|
||||
'artistid' => array(
|
||||
'name' => 'Artist ID',
|
||||
'type' => 'number',
|
||||
)
|
||||
),
|
||||
'Label Releases' => array(
|
||||
'labelid' => array(
|
||||
'name' => 'Label ID',
|
||||
'type' => 'number',
|
||||
)
|
||||
),
|
||||
'User Wantlist' => array(
|
||||
'username_wantlist' => array(
|
||||
'name' => 'Username',
|
||||
'type' => 'text',
|
||||
)
|
||||
),
|
||||
'User Folder' => array(
|
||||
'username_folder' => array(
|
||||
'name' => 'Username',
|
||||
'type' => 'text',
|
||||
),
|
||||
'folderid' => array(
|
||||
'name' => 'Folder ID',
|
||||
'type' => 'number',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
if(!empty($this->getInput('artistid')) || !empty($this->getInput('labelid'))) {
|
||||
|
||||
if(!empty($this->getInput('artistid'))) {
|
||||
$data = getContents("https://api.discogs.com/artists/"
|
||||
. $this->getInput('artistid')
|
||||
. "/releases?sort=year&sort_order=desc")
|
||||
or returnServerError("Unable to query discogs !");
|
||||
} elseif(!empty($this->getInput('labelid'))) {
|
||||
$data = getContents("https://api.discogs.com/labels/"
|
||||
. $this->getInput('labelid')
|
||||
. "/releases?sort=year&sort_order=desc")
|
||||
or returnServerError("Unable to query discogs !");
|
||||
}
|
||||
|
||||
$jsonData = json_decode($data, true);
|
||||
foreach($jsonData["releases"] as $release) {
|
||||
|
||||
$item = array();
|
||||
$item["author"] = $release["artist"];
|
||||
$item["title"] = $release["title"];
|
||||
$item["id"] = $release["id"];
|
||||
$resId = array_key_exists("main_release", $release) ? $release["main_release"] : $release["id"];
|
||||
$item["uri"] = self::URI . $this->getInput('artistid') . "/release/" . $resId;
|
||||
$item["timestamp"] = DateTime::createFromFormat("Y", $release["year"])->getTimestamp();
|
||||
$item["content"] = $item["author"] . " - " . $item["title"];
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
} elseif(!empty($this->getInput("username_wantlist")) || !empty($this->getInput("username_folder"))) {
|
||||
|
||||
if(!empty($this->getInput("username_wantlist"))) {
|
||||
$data = getContents("https://api.discogs.com/users/"
|
||||
. $this->getInput('username_wantlist')
|
||||
. "/wants?sort=added&sort_order=desc")
|
||||
or returnServerError("Unable to query discogs !");
|
||||
$jsonData = json_decode($data, true)["wants"];
|
||||
|
||||
} elseif(!empty($this->getInput("username_folder"))) {
|
||||
$data = getContents("https://api.discogs.com/users/"
|
||||
. $this->getInput('username_folder')
|
||||
. "/collection/folders/"
|
||||
. $this->getInput("folderid")
|
||||
."/releases?sort=added&sort_order=desc")
|
||||
or returnServerError("Unable to query discogs !");
|
||||
$jsonData = json_decode($data, true)["releases"];
|
||||
}
|
||||
foreach($jsonData as $element) {
|
||||
|
||||
$infos = $element["basic_information"];
|
||||
$item = array();
|
||||
$item["title"] = $infos["title"];
|
||||
$item["author"] = $infos["artists"][0]["name"];
|
||||
$item["id"] = $infos["artists"][0]["id"];
|
||||
$item["uri"] = self::URI . $infos["artists"][0]["id"] . "/release/" . $infos["id"];
|
||||
$item["timestamp"] = strtotime($element["date_added"]);
|
||||
$item["content"] = $item["author"] . " - " . $item["title"];
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return static::NAME;
|
||||
}
|
||||
}
|
91
bridges/DribbbleBridge.php
Normal file
91
bridges/DribbbleBridge.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
class DribbbleBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'quentinus95';
|
||||
const NAME = 'Dribbble popular shots';
|
||||
const URI = 'https://dribbble.com';
|
||||
const CACHE_TIMEOUT = 1800;
|
||||
const DESCRIPTION = 'Returns the newest popular shots from Dribbble.';
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . '/shots')
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
||||
$json = $this->loadEmbeddedJsonData($html);
|
||||
|
||||
foreach($html->find('li[id^="screenshot-"]') as $shot) {
|
||||
$item = [];
|
||||
|
||||
$additional_data = $this->findJsonForShot($shot, $json);
|
||||
if ($additional_data === null) {
|
||||
$item['uri'] = self::URI . $shot->find('a', 0)->href;
|
||||
$item['title'] = $shot->find('.dribbble-over strong', 0)->plaintext;
|
||||
} else {
|
||||
$item['timestamp'] = strtotime($additional_data['published_at']);
|
||||
$item['uri'] = self::URI . $additional_data['path'];
|
||||
$item['title'] = $additional_data['title'];
|
||||
}
|
||||
|
||||
$item['author'] = trim($shot->find('.attribution-user a', 0)->plaintext);
|
||||
|
||||
$description = $shot->find('.comment', 0);
|
||||
$item['content'] = $description === null ? '' : $description->plaintext;
|
||||
|
||||
$preview_path = $shot->find('picture source', 0)->attr['srcset'];
|
||||
$item['content'] .= $this->getImageTag($preview_path, $item['title']);
|
||||
$item['enclosures'] = [$this->getFullSizeImagePath($preview_path)];
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function loadEmbeddedJsonData($html){
|
||||
$json = [];
|
||||
$scripts = $html->find('script');
|
||||
|
||||
foreach($scripts as $script) {
|
||||
if(strpos($script->innertext, 'newestShots') !== false) {
|
||||
// fix single quotes
|
||||
$script->innertext = str_replace('\'', '"', $script->innertext);
|
||||
|
||||
// fix JavaScript JSON (why do they not adhere to the standard?)
|
||||
$script->innertext = preg_replace('/(\w+):/i', '"\1":', $script->innertext);
|
||||
|
||||
// find beginning of JSON array
|
||||
$start = strpos($script->innertext, '[');
|
||||
|
||||
// find end of JSON array, compensate for missing character!
|
||||
$end = strpos($script->innertext, '];') + 1;
|
||||
|
||||
// convert JSON to PHP array
|
||||
$json = json_decode(substr($script->innertext, $start, $end - $start), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
private function findJsonForShot($shot, $json){
|
||||
foreach($json as $element) {
|
||||
if(strpos($shot->getAttribute('id'), (string)$element['id']) !== false) {
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getImageTag($preview_path, $title){
|
||||
return sprintf(
|
||||
'<br /> <a href="%s"><img src="%s" alt="%s" /></a>',
|
||||
$this->getFullSizeImagePath($preview_path),
|
||||
$preview_path,
|
||||
$title
|
||||
);
|
||||
}
|
||||
|
||||
private function getFullSizeImagePath($preview_path){
|
||||
return str_replace('_1x', '', $preview_path);
|
||||
}
|
||||
}
|
142
bridges/ETTVBridge.php
Normal file
142
bridges/ETTVBridge.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
class ETTVBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = "GregThib";
|
||||
const NAME = 'ETTV';
|
||||
const URI = 'https://www.ettv.tv/';
|
||||
const DESCRIPTION = 'Returns list of 20 latest torrents for a specific search.';
|
||||
const CACHE_TIMEOUT = 14400; // 4 hours
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'query' => array(
|
||||
'name' => 'Keywords',
|
||||
'required' => true
|
||||
),
|
||||
'cat' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Category',
|
||||
'values' => array(
|
||||
'(ALL TYPES)' => '0',
|
||||
'Anime: Movies' => '73',
|
||||
'Anime: Dubbed/Subbed' => '74',
|
||||
'Anime: Others' => '75',
|
||||
'Books: Ebooks' => '53',
|
||||
'Books: Magazines' => '54',
|
||||
'Books: Comics' => '55',
|
||||
'Books: Audio' => '56',
|
||||
'Books: Others' => '68',
|
||||
'Games: Windows' => '57',
|
||||
'Games: Android' => '58',
|
||||
'Games: Others' => '71',
|
||||
'Movies: HD 1080p' => '1',
|
||||
'Movies: HD 720p' => '2',
|
||||
'Movies: UltraHD/4K' => '3',
|
||||
'Movies: XviD' => '42',
|
||||
'Movies: X264/H264' => '47',
|
||||
'Movies: 3D' => '49',
|
||||
'Movies: Dubs/Dual Audio' => '51',
|
||||
'Movies: CAM/TS' => '65',
|
||||
'Movies: BluRay Disc/Remux' => '66',
|
||||
'Movies: DVDR' => '67',
|
||||
'Movies: HEVC/x265' => '76',
|
||||
'Music: MP3' => '59',
|
||||
'Music: FLAC' => '60',
|
||||
'Music: Music Videos' => '61',
|
||||
'Music: Others' => '69',
|
||||
'Software: Windows' => '62',
|
||||
'Software: Android' => '63',
|
||||
'Software: Mac' => '64',
|
||||
'Software: Others' => '70',
|
||||
'TV: HD/X264/H264' => '41',
|
||||
'TV: SD/X264/H264' => '5',
|
||||
'TV: TV Packs' => '7',
|
||||
'TV: SD/XVID' => '50',
|
||||
'TV: Sport' => '72',
|
||||
'TV: HEVC/x265' => '77',
|
||||
'Unsorted: Unsorted' => '78'
|
||||
),
|
||||
'defaultValue' => '(ALL TYPES)'
|
||||
),
|
||||
'status' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Status',
|
||||
'values' => array(
|
||||
'Active Transfers' => '0',
|
||||
'Included Dead' => '1',
|
||||
'Only Dead' => '2'
|
||||
),
|
||||
'defaultValue' => 'Included Dead'
|
||||
),
|
||||
'lang' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Lang',
|
||||
'values' => array(
|
||||
'(ALL)' => '0',
|
||||
'Arabic' => '17',
|
||||
'Chinese ' => '10',
|
||||
'Danish' => '13',
|
||||
'Dutch' => '11',
|
||||
'English' => '1',
|
||||
'Finnish' => '18',
|
||||
'French' => '2',
|
||||
'German' => '3',
|
||||
'Greek' => '15',
|
||||
'Hindi' => '8',
|
||||
'Italian' => '4',
|
||||
'Japanese' => '5',
|
||||
'Korean' => '9',
|
||||
'Polish' => '14',
|
||||
'Russian' => '7',
|
||||
'Spanish' => '6',
|
||||
'Turkish' => '16'
|
||||
),
|
||||
'defaultValue' => '(ALL)'
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
// No control on inputs, because all have defaultValue set
|
||||
$query_str = 'torrents-search.php';
|
||||
$query_str .= '?search=' . urlencode('+'.str_replace(' ', ' +', $this->getInput('query')));
|
||||
$query_str .= '&cat=' . $this->getInput('cat');
|
||||
$query_str .= 'incldead&=' . $this->getInput('status');
|
||||
$query_str .= '&lang=' . $this->getInput('lang');
|
||||
$query_str .= '&sort=id&order=desc';
|
||||
|
||||
// Get results page
|
||||
$html = getSimpleHTMLDOM(self::URI . $query_str)
|
||||
or returnServerError('Could not request ' . $this->getName());
|
||||
|
||||
// Loop on each entry
|
||||
foreach($html->find('table.table tr') as $element) {
|
||||
if($element->parent->tag == 'thead') continue;
|
||||
$entry = $element->find('td', 1)->find('a', 0);
|
||||
|
||||
// retrieve result page to get more details
|
||||
$link = rtrim(self::URI, "/") . $entry->href;
|
||||
$page = getSimpleHTMLDOM($link)
|
||||
or returnServerError('Could not request page ' . $link);
|
||||
|
||||
// get details & download links
|
||||
$details = $page->find('fieldset.download table', 0); // WHAT?? It should be the second one…
|
||||
$dllinks = $page->find('div#downloadbox table', 0);
|
||||
|
||||
// fill item
|
||||
$item = array();
|
||||
$item['author'] = $details->children(6)->children(1)->plaintext;
|
||||
$item['title'] = $entry->title;
|
||||
$item['uri'] = $dllinks->children(0)->children(0)->children(0)->href;
|
||||
$item['timestamp'] = strtotime($details->children(7)->children(1)->plaintext);
|
||||
$item['content'] = '';
|
||||
$item['content'] .= '<br/><b>Name: </b>' . $details->children(0)->children(1)->innertext;
|
||||
$item['content'] .= '<br/><b>Lang: </b>' . $details->children(3)->children(1)->innertext;
|
||||
$item['content'] .= '<br/><b>Size: </b>' . $details->children(4)->children(1)->innertext;
|
||||
$item['content'] .= '<br/><b>Hash: </b>' . $details->children(5)->children(1)->innertext;
|
||||
foreach($dllinks->children(0)->children(1)->find('a') as $dl) {
|
||||
$item['content'] .= '<br/>' . $dl->outertext;
|
||||
}
|
||||
$item['content'] .= '<br/><br/>' . $details->children(1)->children(0)->innertext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
146
bridges/ElloBridge.php
Normal file
146
bridges/ElloBridge.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
class ElloBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'Ello Bridge';
|
||||
const URI = 'https://ello.co/';
|
||||
const CACHE_TIMEOUT = 4800; //2hours
|
||||
const DESCRIPTION = 'Returns the newest posts for Ello';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'By User' => array(
|
||||
'u' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'title' => 'Username'
|
||||
)
|
||||
),
|
||||
'Search' => array(
|
||||
's' => array(
|
||||
'name' => 'Search',
|
||||
'required' => true,
|
||||
'title' => 'Search'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$header = array(
|
||||
'Authorization: Bearer ' . $this->getAPIKey()
|
||||
);
|
||||
|
||||
if(!empty($this->getInput('u'))) {
|
||||
$postData = getContents(self::URI . 'api/v2/users/~' . urlencode($this->getInput('u')) . '/posts', $header) or
|
||||
returnServerError('Unable to query Ello API.');
|
||||
} else {
|
||||
$postData = getContents(self::URI . 'api/v2/posts?terms=' . urlencode($this->getInput('s')), $header) or
|
||||
returnServerError('Unable to query Ello API.');
|
||||
}
|
||||
|
||||
$postData = json_decode($postData);
|
||||
$count = 0;
|
||||
foreach($postData->posts as $post) {
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $this->getUsername($post, $postData);
|
||||
$item['timestamp'] = strtotime($post->created_at);
|
||||
$item['title'] = $this->findText($post->summary);
|
||||
$item['content'] = $this->getPostContent($post->body);
|
||||
$item['enclosures'] = $this->getEnclosures($post, $postData);
|
||||
$content = $post->body;
|
||||
|
||||
$this->items[] = $item;
|
||||
$count += 1;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function findText($path) {
|
||||
|
||||
foreach($path as $summaryElement) {
|
||||
|
||||
if($summaryElement->kind == 'text') {
|
||||
return $summaryElement->data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
}
|
||||
|
||||
public function getPostContent($path) {
|
||||
|
||||
$content = '';
|
||||
foreach($path as $summaryElement) {
|
||||
|
||||
if($summaryElement->kind == 'text') {
|
||||
$content .= $summaryElement->data;
|
||||
} elseif ($summaryElement->kind == 'image') {
|
||||
$alt = '';
|
||||
if(property_exists($summaryElement->data, 'alt')) {
|
||||
$alt = $summaryElement->data->alt;
|
||||
}
|
||||
$content .= '<img src="' . $summaryElement->data->url . '" alt="' . $alt . '" />';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $content;
|
||||
|
||||
}
|
||||
|
||||
public function getEnclosures($post, $postData) {
|
||||
|
||||
$assets = [];
|
||||
foreach($post->links->assets as $asset) {
|
||||
foreach($postData->linked->assets as $assetLink) {
|
||||
if($asset == $assetLink->id) {
|
||||
$assets[] = $assetLink->attachment->original->url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $assets;
|
||||
|
||||
}
|
||||
|
||||
public function getUsername($post, $postData) {
|
||||
|
||||
foreach($postData->linked->users as $user) {
|
||||
if($user->id == $post->links->author->id) {
|
||||
return $user->username;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getAPIKey() {
|
||||
$cache = Cache::create('FileCache');
|
||||
$cache->setPath(CACHE_DIR);
|
||||
$cache->setParameters(['key']);
|
||||
$key = $cache->loadData();
|
||||
|
||||
if($key == null) {
|
||||
$keyInfo = getContents(self::URI . 'api/webapp-token') or
|
||||
returnServerError('Unable to get token.');
|
||||
$key = json_decode($keyInfo)->token->access_token;
|
||||
$cache->saveData($key);
|
||||
}
|
||||
|
||||
return $key;
|
||||
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return $this->getInput('u') . ' - Ello Bridge';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
}
|
54
bridges/FDroidBridge.php
Normal file
54
bridges/FDroidBridge.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
class FDroidBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Mitsukarenai';
|
||||
const NAME = 'F-Droid Bridge';
|
||||
const URI = 'https://f-droid.org/';
|
||||
const CACHE_TIMEOUT = 60 * 60 * 2; // 2 hours
|
||||
const DESCRIPTION = 'Returns latest added/updated apps on the open-source Android apps repository F-Droid';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'u' => array(
|
||||
'name' => 'Widget selection',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'values' => array(
|
||||
'Latest added apps' => 'added',
|
||||
'Latest updated apps' => 'updated'
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$url = self::URI;
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request F-Droid.');
|
||||
|
||||
// targetting the corresponding widget based on user selection
|
||||
// "updated" is the 4th widget on the page, "added" is the 5th
|
||||
|
||||
switch($this->getInput('u')) {
|
||||
case 'updated':
|
||||
$html_widget = $html->find('div.sidebar-widget', 4);
|
||||
break;
|
||||
default:
|
||||
$html_widget = $html->find('div.sidebar-widget', 5);
|
||||
break;
|
||||
}
|
||||
|
||||
// and now extracting app info from the selected widget (and yeah turns out icons are of heterogeneous sizes)
|
||||
|
||||
foreach($html_widget->find('a') as $element) {
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $element->href;
|
||||
$item['title'] = $element->find('h4', 0)->plaintext;
|
||||
$item['icon'] = $element->find('img', 0)->src;
|
||||
$item['summary'] = $element->find('span.package-summary', 0)->plaintext;
|
||||
$item['content'] = '
|
||||
<a href="'.$item['uri'].'">
|
||||
<img alt="" style="max-height:128px" src="'.$item['icon'].'">
|
||||
</a><br>'.$item['summary'];
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -46,7 +46,7 @@ class FacebookBridge extends BridgeAbstract {
|
||||
if(is_array($matches) && count($matches) > 1) {
|
||||
$link = $matches[1];
|
||||
if(strpos($link, '/') === 0)
|
||||
$link = self::URI . $link . '"';
|
||||
$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 . '"';
|
||||
@@ -96,17 +96,15 @@ class FacebookBridge extends BridgeAbstract {
|
||||
$captcha_action = $_SESSION['captcha_action'];
|
||||
$captcha_fields = $_SESSION['captcha_fields'];
|
||||
$captcha_fields['captcha_response'] = preg_replace("/[^a-zA-Z0-9]+/", "", $_POST['captcha_response']);
|
||||
$http_options = array(
|
||||
'http' => array(
|
||||
'method' => 'POST',
|
||||
'user_agent' => ini_get('user_agent'),
|
||||
'header' => array("Content-type:
|
||||
application/x-www-form-urlencoded\r\nReferer: $captcha_action\r\nCookie: noscript=1\r\n"),
|
||||
'content' => http_build_query($captcha_fields)
|
||||
),
|
||||
|
||||
$header = array("Content-type:
|
||||
application/x-www-form-urlencoded\r\nReferer: $captcha_action\r\nCookie: noscript=1\r\n");
|
||||
$opts = array(
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_POSTFIELDS => http_build_query($captcha_fields)
|
||||
);
|
||||
$context = stream_context_create($http_options);
|
||||
$html = getContents($captcha_action, false, $context);
|
||||
|
||||
$html = getContents($captcha_action, $header, $opts);
|
||||
|
||||
if($html === false) {
|
||||
returnServerError('Failed to submit captcha response back to Facebook');
|
||||
@@ -120,23 +118,18 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
//Retrieve page contents
|
||||
if(is_null($html)) {
|
||||
$http_options = array(
|
||||
'http' => array(
|
||||
'method' => 'GET',
|
||||
'user_agent' => ini_get('user_agent'),
|
||||
'header' => 'Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n"
|
||||
)
|
||||
);
|
||||
$context = stream_context_create($http_options);
|
||||
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n");
|
||||
|
||||
// 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',
|
||||
false,
|
||||
$context)
|
||||
$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',
|
||||
false,
|
||||
$context)
|
||||
$html = getSimpleHTMLDOM(self::URI . 'pages/' . $this->getInput('u') . '?_fb_noscript=1', $header)
|
||||
or returnServerError('No results for this query.');
|
||||
}
|
||||
}
|
||||
@@ -155,7 +148,7 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
//Show captcha filling form to the viewer, proxying the captcha image
|
||||
$img = base64_encode(getContents($captcha->find('img', 0)->src));
|
||||
header('HTTP/1.1 500 ' . Http::getMessageForCode(500));
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/html');
|
||||
$message = <<<EOD
|
||||
<form method="post" action="?{$_SERVER['QUERY_STRING']}">
|
||||
@@ -171,6 +164,12 @@ EOD;
|
||||
}
|
||||
|
||||
//No captcha? We can carry on retrieving page contents :)
|
||||
//First, we check wether the page is public or not
|
||||
$loginForm = $html->find('._585r', 0);
|
||||
if($loginForm != null) {
|
||||
returnServerError('You must be logged in to view this page. This is not supported by RSS-Bridge.');
|
||||
}
|
||||
|
||||
$element = $html
|
||||
->find('#pagelet_timeline_main_column')[0]
|
||||
->children(0)
|
||||
@@ -281,9 +280,11 @@ EOD;
|
||||
if(strlen($title) > 64)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
|
||||
|
||||
$uri = self::URI . $post->find('abbr')[0]->parent()->getAttribute('href');
|
||||
|
||||
//Build and add final item
|
||||
$item['uri'] = self::URI . $post->find('abbr')[0]->parent()->getAttribute('href');
|
||||
$item['content'] = $content;
|
||||
$item['uri'] = htmlspecialchars_decode($uri);
|
||||
$item['content'] = htmlspecialchars_decode($content);
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['timestamp'] = $date;
|
||||
|
@@ -10,6 +10,7 @@ class GelbooruBridge extends DanbooruBridge {
|
||||
|
||||
const PATHTODATA = '.thumb';
|
||||
const IDATTRIBUTE = 'id';
|
||||
const TAGATTRIBUTE = 'title';
|
||||
|
||||
const PIDBYPAGE = 63;
|
||||
|
||||
@@ -19,4 +20,16 @@ class GelbooruBridge extends DanbooruBridge {
|
||||
. ($this->getInput('p') ? ($this->getInput('p') - 1) * static::PIDBYPAGE : '')
|
||||
. '&tags=' . urlencode($this->getInput('t'));
|
||||
}
|
||||
|
||||
protected function getTags($element){
|
||||
$tags = parent::getTags($element);
|
||||
$tags = explode(' ', $tags);
|
||||
|
||||
// Remove statistics from the tags list (identified by colon)
|
||||
foreach($tags as $key => $tag) {
|
||||
if(strpos($tag, ':') !== false) unset($tags[$key]);
|
||||
}
|
||||
|
||||
return implode(' ', $tags);
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ class GoComicsBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'sky';
|
||||
const NAME = 'GoComics Unofficial RSS';
|
||||
const URI = 'http://www.gocomics.com/';
|
||||
const URI = 'https://www.gocomics.com/';
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'The Unofficial GoComics RSS';
|
||||
const PARAMETERS = array( array(
|
||||
@@ -18,25 +18,27 @@ class GoComicsBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request GoComics: ' . $this->getURI());
|
||||
|
||||
foreach($html->find('div.item-comic-container') as $element) {
|
||||
//Get info from first page
|
||||
$author = preg_replace('/By /', '', $html->find(".media-subheading", 0)->plaintext);
|
||||
|
||||
$img = $element->find('img', 0);
|
||||
$link = $element->find('a.item-comic-link', 0);
|
||||
$comic = $img->src;
|
||||
$title = $link->title;
|
||||
$url = $html->find('input.js-copy-link', 0)->value;
|
||||
$date = substr($title, -10);
|
||||
if (empty($title))
|
||||
$title = 'GoComics ' . $this->getInput('comicname') . ' on ' . $date;
|
||||
$date = strtotime($date);
|
||||
$link = self::URI . $html->find(".gc-deck--cta-0", 0)->find('a', 0)->href;
|
||||
for($i = 0; $i < 5; $i++) {
|
||||
|
||||
$item = array();
|
||||
$item['id'] = $url;
|
||||
$item['uri'] = $url;
|
||||
$item['title'] = $title;
|
||||
$item['author'] = preg_replace('/by /', '', $element->find('a.link-blended small', 0)->plaintext);
|
||||
$item['timestamp'] = $date;
|
||||
$item['content'] = '<img src="' . $comic . '" alt="' . $title . '" />';
|
||||
|
||||
$page = getSimpleHTMLDOM($link)
|
||||
or returnServerError('Could not request GoComics: ' . $link);
|
||||
$imagelink = $page->find(".img-fluid", 1)->src;
|
||||
$date = explode("/", $link);
|
||||
|
||||
$item['id'] = $imagelink;
|
||||
$item['uri'] = $link;
|
||||
$item['author'] = $author;
|
||||
$item['title'] = 'GoComics ' . $this->getInput('comicname');
|
||||
$item['timestamp'] = DateTime::createFromFormat("Ymd", $date[5] . $date[6] . $date[7])->getTimestamp();
|
||||
$item['content'] = '<img src="' . $imagelink . '" />';
|
||||
|
||||
$link = self::URI . $page->find(".js-previous-comic", 0)->href;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
310
bridges/IPBBridge.php
Normal file
310
bridges/IPBBridge.php
Normal file
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
class IPBBridge extends FeedExpander {
|
||||
|
||||
const NAME = 'IPB Bridge';
|
||||
const URI = 'https://www.invisionpower.com';
|
||||
const DESCRIPTION = 'Returns feeds for forums powered by IPB';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'uri' => array(
|
||||
'name' => 'URI',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Insert forum, subforum or topic URI',
|
||||
'exampleValue' => 'https://invisioncommunity.com/forums/forum/499-feedback-and-ideas/'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Specifies the number of items to return on each request (-1: all)',
|
||||
'defaultValue' => 10
|
||||
)
|
||||
)
|
||||
);
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
// Constants for internal use
|
||||
const FORUM_TYPE_LIST_FILTER = '.cForumTopicTable';
|
||||
const FORUM_TYPE_TABLE_FILTER = '#forum_table';
|
||||
|
||||
const TOPIC_TYPE_ARTICLE = 'article';
|
||||
const TOPIC_TYPE_DIV = 'div.post_block';
|
||||
|
||||
public function getURI(){
|
||||
return $this->getInput('uri') ?: parent::getURI();
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
// The URI cannot be the mainpage (or anything related)
|
||||
switch(parse_url($this->getInput('uri'), PHP_URL_PATH)) {
|
||||
case null:
|
||||
case '/index.php':
|
||||
returnClientError('Provided URI is invalid!');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Sanitize the URI (because else it won't work)
|
||||
$uri = rtrim($this->getInput('uri'), '/'); // No trailing slashes!
|
||||
|
||||
// Forums might provide feeds, though that's optional *facepalm*
|
||||
// Let's check if there is a valid feed available
|
||||
$headers = get_headers($uri . '.xml');
|
||||
|
||||
if($headers[0] === 'HTTP/1.1 200 OK') { // Heureka! It's a valid feed!
|
||||
return $this->collectExpandableDatas($uri);
|
||||
}
|
||||
|
||||
// No valid feed, so do it the hard way
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not request ' . $this->getInput('uri') . '!');
|
||||
|
||||
$limit = $this->getInput('limit');
|
||||
|
||||
// Determine if this is a topic or a forum
|
||||
switch(true) {
|
||||
case $this->isTopic($html):
|
||||
$this->collectTopic($html, $limit);
|
||||
break;
|
||||
case $this->isForum($html);
|
||||
$this->collectForum($html);
|
||||
break;
|
||||
default:
|
||||
returnClientError('Unknown type!');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function isForum($html){
|
||||
return !is_null($html->find('div[data-controller*=forums.front.forum.forumPage]', 0))
|
||||
|| !is_null($html->find(static::FORUM_TYPE_TABLE_FILTER, 0));
|
||||
}
|
||||
|
||||
private function isTopic($html){
|
||||
return !is_null($html->find('div[data-controller*=core.front.core.commentFeed]', 0))
|
||||
|| !is_null($html->find(static::TOPIC_TYPE_DIV, 0));
|
||||
}
|
||||
|
||||
private function collectForum($html){
|
||||
// There are multiple forum designs in use (depends on version?)
|
||||
// 1 - Uses an ordered list (based on https://invisioncommunity.com/forums)
|
||||
// 2 - Uses a table (based on https://onehallyu.com)
|
||||
|
||||
switch(true) {
|
||||
case !is_null($html->find(static::FORUM_TYPE_LIST_FILTER, 0)):
|
||||
$this->collectForumList($html);
|
||||
break;
|
||||
case !is_null($html->find(static::FORUM_TYPE_TABLE_FILTER, 0)):
|
||||
$this->collectForumTable($html);
|
||||
break;
|
||||
default:
|
||||
returnClientError('Unknown forum format!');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectForumList($html){
|
||||
foreach($html->find(static::FORUM_TYPE_LIST_FILTER, 0)->children() as $row) {
|
||||
// Columns: Title, Statistics, Last modified
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $row->find('a', 0)->href;
|
||||
$item['title'] = $row->find('a', 0)->title;
|
||||
$item['author'] = $row->find('a', 1)->innertext;
|
||||
$item['timestamp'] = strtotime($row->find('time', 0)->getAttribute('datetime'));
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectForumTable($html){
|
||||
foreach($html->find(static::FORUM_TYPE_TABLE_FILTER, 0)->children() as $row) {
|
||||
// Columns: Icon, Content, Preview, Statistics, Last modified
|
||||
$item = array();
|
||||
|
||||
// Skip header row
|
||||
if(!is_null($row->find('th', 0))) continue;
|
||||
|
||||
$item['uri'] = $row->find('a', 0)->href;
|
||||
$item['title'] = $row->find('.title', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($row->find('[itemprop=dateCreated]', 0)->plaintext);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectTopic($html, $limit){
|
||||
// There are multiple topic designs in use (depends on version?)
|
||||
// 1 - Uses articles (based on https://invisioncommunity.com/forums)
|
||||
// 2 - Uses divs (based on https://onehallyu.com)
|
||||
|
||||
switch(true) {
|
||||
case !is_null($html->find(static::TOPIC_TYPE_ARTICLE, 0)):
|
||||
$this->collectTopicHistory($html, $limit, 'collectTopicArticle');
|
||||
break;
|
||||
case !is_null($html->find(static::TOPIC_TYPE_DIV, 0)):
|
||||
$this->collectTopicHistory($html, $limit, 'collectTopicDiv');
|
||||
break;
|
||||
default:
|
||||
returnClientError('Unknown topic format!');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectTopicHistory($html, $limit, $callback){
|
||||
// Make sure the callback is valid!
|
||||
if(!method_exists($this, $callback))
|
||||
returnServerError('Unknown function (\'' . $callback . '\')!');
|
||||
|
||||
$next = null; // Holds the URI of the next page
|
||||
|
||||
while(true) {
|
||||
$next = $this->$callback($html, is_null($next));
|
||||
|
||||
if(is_null($next) || ($limit > 0 && count($this->items) >= $limit)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOMCached($next);
|
||||
}
|
||||
|
||||
// We might have more items than specified, remove excess
|
||||
$this->items = array_slice($this->items, 0, $limit);
|
||||
}
|
||||
|
||||
private function collectTopicArticle($html, $firstrun = true){
|
||||
$title = $html->find('h1.ipsType_pageTitle', 0)->plaintext;
|
||||
|
||||
// Are we on last page?
|
||||
if($firstrun && !is_null($html->find('.ipsPagination', 0))) {
|
||||
$last = $html->find('.ipsPagination_last a', 0)->{'data-page'};
|
||||
$active = $html->find('.ipsPagination_active a', 0)->{'data-page'};
|
||||
|
||||
if($active !== $last) {
|
||||
// Load last page into memory (cached)
|
||||
$html = getSimpleHTMLDOMCached($html->find('.ipsPagination_last a', 0)->href);
|
||||
}
|
||||
}
|
||||
|
||||
foreach(array_reverse($html->find(static::TOPIC_TYPE_ARTICLE)) as $article) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $article->find('time', 0)->parent()->href;
|
||||
$item['author'] = $article->find('aside a', 0)->plaintext;
|
||||
$item['title'] = $item['author'] . ' - ' . $title;
|
||||
$item['timestamp'] = strtotime($article->find('time', 0)->getAttribute('datetime'));
|
||||
|
||||
$content = $article->find('[data-role=commentContent]', 0);
|
||||
$content = $this->scaleImages($content);
|
||||
$item['content'] = $this->fixContent($content);
|
||||
$item['enclosures'] = $this->findImages($article->find('[data-role=commentContent]', 0)) ?: null;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
// Return whatever page comes next (previous, as we add in inverse order)
|
||||
// Do we have a previous page? (inactive means no)
|
||||
if(!is_null($html->find('li[class=ipsPagination_prev ipsPagination_inactive]', 0))) {
|
||||
return null; // No, or no more
|
||||
} elseif(!is_null($html->find('li[class=ipsPagination_prev]', 0))) {
|
||||
return $html->find('.ipsPagination_prev a', 0)->href;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function collectTopicDiv($html, $firstrun = true){
|
||||
$title = $html->find('h1.ipsType_pagetitle', 0)->plaintext;
|
||||
|
||||
// Are we on last page?
|
||||
if($firstrun && !is_null($html->find('.pagination', 0))) {
|
||||
|
||||
$active = $html->find('li[class=page active]', 0)->plaintext;
|
||||
|
||||
// There are two ways the 'last' page is displayed:
|
||||
// - With a distict 'last' button (only if there are enough pages)
|
||||
// - With a button for each page (use last button)
|
||||
if(!is_null($html->find('li.last', 0))) {
|
||||
$last = $html->find('li.last a', 0);
|
||||
} else {
|
||||
$last = $html->find('li[class=page] a', -1);
|
||||
}
|
||||
|
||||
if($active !== $last->plaintext) {
|
||||
// Load last page into memory (cached)
|
||||
$html = getSimpleHTMLDOMCached($last->href);
|
||||
}
|
||||
}
|
||||
|
||||
foreach(array_reverse($html->find(static::TOPIC_TYPE_DIV)) as $article) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $article->find('a[rel=bookmark]', 0)->href;
|
||||
$item['author'] = $article->find('.author', 0)->plaintext;
|
||||
$item['title'] = $item['author'] . ' - ' . $title;
|
||||
$item['timestamp'] = strtotime($article->find('.published', 0)->getAttribute('title'));
|
||||
|
||||
$content = $article->find('[itemprop=commentText]', 0);
|
||||
$content = $this->scaleImages($content);
|
||||
$item['content'] = $this->fixContent($content);
|
||||
|
||||
$item['enclosures'] = $this->findImages($article->find('.post_body', 0)) ?: null;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
// Return whatever page comes next (previous, as we add in inverse order)
|
||||
// Do we have a previous page?
|
||||
if(!is_null($html->find('li.prev', 0))) {
|
||||
return $html->find('li.prev a', 0)->href;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns all images from the provide HTML DOM */
|
||||
private function findImages($html){
|
||||
$images = array();
|
||||
|
||||
foreach($html->find('img') as $img) {
|
||||
$images[] = $img->src;
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/** Sets the maximum width and height for all images */
|
||||
private function scaleImages($html, $width = 400, $height = 400){
|
||||
foreach($html->find('img') as $img) {
|
||||
$img->style = "max-width: {$width}px; max-height: {$height}px;";
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/** Removes all unnecessary tags and adds formatting */
|
||||
private function fixContent($html){
|
||||
|
||||
// Restore quote highlighting
|
||||
foreach($html->find('blockquote') as $quote) {
|
||||
$quote->style = <<<EOD
|
||||
padding: 0px 15px;
|
||||
border-width: 1px 1px 1px 2px;
|
||||
border-style: solid;
|
||||
border-color: #ededed #e8e8e8 #dbdbdb #666666;
|
||||
background: #fbfbfb;
|
||||
EOD;
|
||||
}
|
||||
|
||||
// Remove unnecessary tags
|
||||
$content = strip_tags(
|
||||
$html->innertext,
|
||||
'<p><a><img><ol><ul><li><table><tr><th><td><strong><blockquote><br><hr><h>'
|
||||
);
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
@@ -6,75 +6,129 @@ class InstagramBridge extends BridgeAbstract {
|
||||
const URI = 'https://instagram.com/';
|
||||
const DESCRIPTION = 'Returns the newest images';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'u' => array(
|
||||
'name' => 'username',
|
||||
'required' => true
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'u' => array(
|
||||
'name' => 'username',
|
||||
'required' => true
|
||||
)
|
||||
),
|
||||
'media_type' => array(
|
||||
'name' => 'Media type',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'Both' => 'all',
|
||||
'Video' => 'video',
|
||||
'Picture' => 'picture'
|
||||
),
|
||||
'defaultValue' => 'all'
|
||||
array(
|
||||
'h' => array(
|
||||
'name' => 'hashtag',
|
||||
'required' => true
|
||||
)
|
||||
),
|
||||
'global' => array(
|
||||
'media_type' => array(
|
||||
'name' => 'Media type',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'All' => 'all',
|
||||
'Story' => 'story',
|
||||
'Video' => 'video',
|
||||
'Picture' => 'picture',
|
||||
),
|
||||
'defaultValue' => 'all'
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request Instagram.');
|
||||
|
||||
$innertext = null;
|
||||
|
||||
foreach($html->find('script') as $script) {
|
||||
if('' === $script->innertext) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pos = strpos(trim($script->innertext), 'window._sharedData');
|
||||
if(0 !== $pos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$innertext = $script->innertext;
|
||||
break;
|
||||
if(!is_null($this->getInput('h')) && $this->getInput('media_type') == 'story') {
|
||||
returnClientError('Stories are not supported for hashtags!');
|
||||
}
|
||||
|
||||
$json = trim(substr($innertext, $pos + 18), ' =;');
|
||||
$data = json_decode($json);
|
||||
$data = $this->getInstagramJSON($this->getURI());
|
||||
|
||||
$userMedia = $data->entry_data->ProfilePage[0]->user->media->nodes;
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
$userMedia = $data->entry_data->ProfilePage[0]->graphql->user->edge_owner_to_timeline_media->edges;
|
||||
} else {
|
||||
$userMedia = $data->entry_data->TagPage[0]->graphql->hashtag->edge_hashtag_to_media->edges;
|
||||
}
|
||||
|
||||
foreach($userMedia as $media) {
|
||||
// Check media type
|
||||
switch($this->getInput('media_type')) {
|
||||
case 'all': break;
|
||||
case 'video':
|
||||
if($media->is_video === false) continue 2;
|
||||
break;
|
||||
case 'picture':
|
||||
if($media->is_video === true) continue 2;
|
||||
break;
|
||||
default: break;
|
||||
$media = $media->node;
|
||||
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
switch($this->getInput('media_type')) {
|
||||
case 'all': break;
|
||||
case 'video':
|
||||
if($media->__typename != 'GraphVideo') continue 2;
|
||||
break;
|
||||
case 'picture':
|
||||
if($media->__typename != 'GraphImage') continue 2;
|
||||
break;
|
||||
case 'story':
|
||||
if($media->__typename != 'GraphSidecar') continue 2;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
if($this->getInput('media_type') == 'video' && !$media->is_video) continue;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . 'p/' . $media->code . '/';
|
||||
$item['content'] = '<img src="' . htmlentities($media->display_src) . '" />';
|
||||
if (isset($media->caption)) {
|
||||
$item['title'] = $media->caption;
|
||||
$item['uri'] = self::URI . 'p/' . $media->shortcode . '/';
|
||||
|
||||
if (isset($media->edge_media_to_caption->edges[0]->node->text)) {
|
||||
$item['title'] = $media->edge_media_to_caption->edges[0]->node->text;
|
||||
} else {
|
||||
$item['title'] = basename($media->display_src);
|
||||
$item['title'] = basename($media->display_url);
|
||||
}
|
||||
$item['timestamp'] = $media->date;
|
||||
|
||||
if(!is_null($this->getInput('u')) && $media->__typename == 'GraphSidecar') {
|
||||
$data = $this->getInstagramStory($item['uri']);
|
||||
$item['content'] = $data[0];
|
||||
$item['enclosures'] = $data[1];
|
||||
} else {
|
||||
$item['content'] = '<img src="' . htmlentities($media->display_url) . '" alt="'. $item["title"] . '" />';
|
||||
$item['enclosures'] = array($media->display_url);
|
||||
}
|
||||
|
||||
$item['timestamp'] = $media->taken_at_timestamp;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getInstagramStory($uri) {
|
||||
|
||||
$data = $this->getInstagramJSON($uri);
|
||||
$mediaInfo = $data->entry_data->PostPage[0]->graphql->shortcode_media;
|
||||
|
||||
//Process the first element, that isn't in the node graph
|
||||
$caption = $mediaInfo->edge_media_to_caption->edges[0]->node->text;
|
||||
|
||||
$enclosures = [$mediaInfo->display_url];
|
||||
$content = '<img src="' . htmlentities($mediaInfo->display_url) . '" alt="'. $caption . '" />';
|
||||
|
||||
foreach($mediaInfo->edge_sidecar_to_children->edges as $media) {
|
||||
|
||||
$content .= '<img src="' . htmlentities($media->node->display_url) . '" alt="'. $caption . '" />';
|
||||
$enclosures[] = $media->node->display_url;
|
||||
|
||||
}
|
||||
|
||||
return [$content, $enclosures];
|
||||
|
||||
}
|
||||
|
||||
protected function getInstagramJSON($uri) {
|
||||
|
||||
$html = getContents($uri)
|
||||
or returnServerError('Could not request Instagram.');
|
||||
$scriptRegex = '/window\._sharedData = (.*);<\/script>/';
|
||||
|
||||
preg_match($scriptRegex, $html, $matches, PREG_OFFSET_CAPTURE, 0);
|
||||
|
||||
return json_decode($matches[1][0]);
|
||||
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return $this->getInput('u') . ' - Instagram Bridge';
|
||||
@@ -86,6 +140,8 @@ class InstagramBridge extends BridgeAbstract {
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return self::URI . urlencode($this->getInput('u'));
|
||||
} elseif(!is_null($this->getInput('h'))) {
|
||||
return self::URI . 'explore/tags/' . urlencode($this->getInput('h'));
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
|
@@ -45,9 +45,7 @@ class KernelBugTrackerBridge extends BridgeAbstract {
|
||||
// We use the print preview page for simplicity
|
||||
$html = getSimpleHTMLDOMCached($this->getURI() . '&format=multiple',
|
||||
86400,
|
||||
false,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
|
@@ -188,9 +188,9 @@ EOD;
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = self::URI.'#'.microtime(true);
|
||||
$item['uri'] = self::URI.'#'.count($items);
|
||||
|
||||
$item['timestamp'] = $this->editionTimeStamp;//+$URICounter;
|
||||
$item['timestamp'] = $this->editionTimeStamp;
|
||||
|
||||
$item['author'] = 'LWN';
|
||||
|
||||
|
0
bridges/LeBonCoinBridge.php
Executable file → Normal file
0
bridges/LeBonCoinBridge.php
Executable file → Normal file
@@ -42,10 +42,10 @@ class LegifranceJOBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or $this->returnServer('Unable to download ' . self::URI);
|
||||
|
||||
$this->author = trim($html->find('h2.title', 0)->plaintext);
|
||||
$this->author = trim($html->find('h2.titleJO', 0)->plaintext);
|
||||
$uri = $html->find('h2.titleELI', 0)->plaintext;
|
||||
$this->uri = trim(substr($uri, strpos($uri, 'https')));
|
||||
$this->timestamp = strtotime(substr($this->uri, strpos($this->uri, 'eli/jo/') + strlen('eli/jo/')));
|
||||
$this->timestamp = strtotime(substr($this->uri, strpos($this->uri, 'eli/jo/') + strlen('eli/jo/'), -5));
|
||||
|
||||
foreach($html->find('h3') as $section) {
|
||||
$subsections = $section->nextSibling()->find('h4');
|
||||
|
@@ -4,7 +4,7 @@ class MixCloudBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Alexis CHEMEL';
|
||||
const NAME = 'MixCloud';
|
||||
const URI = 'https://mixcloud.com/';
|
||||
const URI = 'https://www.mixcloud.com';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Returns latest musics on user stream';
|
||||
|
||||
@@ -24,8 +24,9 @@ class MixCloudBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
ini_set('user_agent', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0');
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . $this->getInput('u'))
|
||||
$html = getSimpleHTMLDOM(self::URI . '/' . $this->getInput('u'))
|
||||
or returnServerError('Could not request MixCloud.');
|
||||
|
||||
foreach($html->find('section.card') as $element) {
|
||||
|
57
bridges/NotAlwaysBridge.php
Normal file
57
bridges/NotAlwaysBridge.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
class NotAlwaysBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'mozes';
|
||||
const NAME = 'Not Always family Bridge';
|
||||
const URI = 'https://notalwaysright.com/';
|
||||
const DESCRIPTION = 'Returns the latest stories';
|
||||
const CACHE_TIMEOUT = 1800; // 30 minutes
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'filter' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Filter',
|
||||
'values' => array(
|
||||
'All' => 'all',
|
||||
'Right' => 'right',
|
||||
'Working' => 'working',
|
||||
'Romantic' => 'romantic',
|
||||
'Related' => 'related',
|
||||
'Learning' => 'learning',
|
||||
'Friendly' => 'friendly',
|
||||
'Hopeless' => 'hopeless',
|
||||
'Unfiltered' => 'unfiltered'
|
||||
),
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request NotAlways.');
|
||||
foreach($html->find('.post') as $post) {
|
||||
#print_r($post);
|
||||
$item = array();
|
||||
$item['uri'] = $post->find('h1', 0)->find('a', 0)->href;
|
||||
$item['content'] = $post;
|
||||
$item['title'] = $post->find('h1', 0)->find('a', 0)->innertext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('filter'))) {
|
||||
return $this->getInput('filter') . ' - NotAlways Bridge';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('filter'))) {
|
||||
return self::URI . $this->getInput('filter') . "/";
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
23
bridges/PcGamerBridge.php
Normal file
23
bridges/PcGamerBridge.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
class PcGamerBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'PC Gamer';
|
||||
const URI = 'https://www.pcgamer.com/';
|
||||
const DESCRIPTION = 'PC Gamer Most Read Stories';
|
||||
const MAINTAINER = 'mdemoss';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOMCached($this->getURI(), 300);
|
||||
$stories = $html->find('div#popularcontent li.most-popular-item');
|
||||
foreach ($stories as $element) {
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
$articleHtml = getSimpleHTMLDOMCached($item['uri']);
|
||||
$item['title'] = $element->find('h4 a', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($articleHtml->find('meta[name=pub_date]', 0)->content);
|
||||
$item['content'] = $articleHtml->find('meta[name=description]', 0)->content;
|
||||
$item['author'] = $articleHtml->find('a[itemprop=author]', 0)->plaintext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,12 +15,6 @@ class PinterestBridge extends FeedExpander {
|
||||
'b' => array(
|
||||
'name' => 'board',
|
||||
'required' => true
|
||||
),
|
||||
'r' => array(
|
||||
'name' => 'Use custom RSS',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Uncheck to return data via custom filters (more data)'
|
||||
)
|
||||
),
|
||||
'From search' => array(
|
||||
@@ -34,12 +28,8 @@ class PinterestBridge extends FeedExpander {
|
||||
public function collectData(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By username and board':
|
||||
if($this->getInput('r')) {
|
||||
$html = getSimpleHTMLDOMCached($this->getURI());
|
||||
$this->getUserResults($html);
|
||||
} else {
|
||||
$this->collectExpandableDatas($this->getURI() . '.rss');
|
||||
}
|
||||
$this->collectExpandableDatas($this->getURI() . '.rss');
|
||||
$this->fixLowRes();
|
||||
break;
|
||||
case 'From search':
|
||||
default:
|
||||
@@ -48,49 +38,17 @@ class PinterestBridge extends FeedExpander {
|
||||
}
|
||||
}
|
||||
|
||||
private function getUserResults($html){
|
||||
$json = json_decode($html->find('#jsInit1', 0)->innertext, true);
|
||||
$results = $json['tree']['children'][0]['children'][0]['children'][0]['options']['props']['data']['board_feed'];
|
||||
$username = $json['resourceDataCache'][0]['data']['owner']['username'];
|
||||
$fullname = $json['resourceDataCache'][0]['data']['owner']['full_name'];
|
||||
$avatar = $json['resourceDataCache'][0]['data']['owner']['image_small_url'];
|
||||
private function fixLowRes() {
|
||||
|
||||
foreach($results as $result) {
|
||||
$item = array();
|
||||
$newitems = [];
|
||||
$pattern = '/https\:\/\/i\.pinimg\.com\/[a-zA-Z0-9]*x\//';
|
||||
foreach($this->items as $item) {
|
||||
|
||||
$item['uri'] = $result['link'];
|
||||
|
||||
// Some use regular titles, others provide 'advanced' infos, a few
|
||||
// provide even less info. Thus we attempt multiple options.
|
||||
$item['title'] = trim($result['title']);
|
||||
|
||||
if($item['title'] === "")
|
||||
$item['title'] = trim($result['rich_summary']['display_name']);
|
||||
|
||||
if($item['title'] === "")
|
||||
$item['title'] = trim($result['description']);
|
||||
|
||||
$item['timestamp'] = strtotime($result['created_at']);
|
||||
$item['username'] = $username;
|
||||
$item['fullname'] = $fullname;
|
||||
$item['avatar'] = $avatar;
|
||||
$item['author'] = $item['username'] . ' (' . $item['fullname'] . ')';
|
||||
$item['content'] = '<img align="left" style="margin: 2px 4px;" src="'
|
||||
. htmlentities($item['avatar'])
|
||||
. '" /><p><strong>'
|
||||
. $item['username']
|
||||
. '</strong><br>'
|
||||
. $item['fullname']
|
||||
. '</p><br><img src="'
|
||||
. $result['images']['736x']['url']
|
||||
. '" alt="" /><br><p>'
|
||||
. $result['description']
|
||||
. '</p>';
|
||||
|
||||
$item['enclosures'] = array($result['images']['orig']['url']);
|
||||
|
||||
$this->items[] = $item;
|
||||
$item["content"] = preg_replace($pattern, 'https://i.pinimg.com/originals/', $item["content"]);
|
||||
$newitems[] = $item;
|
||||
}
|
||||
$this->items = $newitems;
|
||||
|
||||
}
|
||||
|
||||
private function getSearchResults($html){
|
||||
|
73
bridges/PixivBridge.php
Normal file
73
bridges/PixivBridge.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
class PixivBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'Pixiv Bridge';
|
||||
const URI = 'https://www.pixiv.net/';
|
||||
const DESCRIPTION = 'Returns the tag search from pixiv.net';
|
||||
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'tag' => array(
|
||||
'name' => 'Tag to search',
|
||||
'exampleValue' => 'example',
|
||||
'required' => true
|
||||
),
|
||||
));
|
||||
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getContents(static::URI.'search.php?word=' . urlencode($this->getInput('tag')))
|
||||
or returnClientError('Unable to query pixiv.net');
|
||||
$regex = '/<input type="hidden"id="js-mount-point-search-result-list"data-items="([^"]*)/';
|
||||
$timeRegex = '/img\/([0-9]{4})\/([0-9]{2})\/([0-9]{2})\/([0-9]{2})\/([0-9]{2})\/([0-9]{2})\//';
|
||||
|
||||
preg_match_all($regex, $html, $matches, PREG_SET_ORDER, 0);
|
||||
if(!$matches) return;
|
||||
|
||||
$content = json_decode(html_entity_decode($matches[0][1]), true);
|
||||
$count = 0;
|
||||
foreach($content as $result) {
|
||||
if($count == 10) break;
|
||||
$count++;
|
||||
|
||||
$item = array();
|
||||
$item["id"] = $result["illustId"];
|
||||
$item["uri"] = "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=" . $result["illustId"];
|
||||
$item["title"] = $result["illustTitle"];
|
||||
$item["author"] = $result["userName"];
|
||||
|
||||
preg_match_all($timeRegex, $result["url"], $dt, PREG_SET_ORDER, 0);
|
||||
$elementDate = DateTime::createFromFormat("YmdHis",
|
||||
$dt[0][1] . $dt[0][2] . $dt[0][3] . $dt[0][4] . $dt[0][5] . $dt[0][6]);
|
||||
$item["timestamp"] = $elementDate->getTimestamp();
|
||||
|
||||
$item["content"] = "<img src='" . $this->cacheImage($result['url'], $item["id"]) . "' />";
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function cacheImage($url, $illustId) {
|
||||
|
||||
$url = str_replace("_master1200", "", $url);
|
||||
$url = str_replace("c/240x240/img-master/", "img-original/", $url);
|
||||
$path = CACHE_DIR . '/pixiv_img';
|
||||
|
||||
if(!is_dir($path))
|
||||
mkdir($path, 0755, true);
|
||||
|
||||
if(!is_file($path . '/' . $illustId . '.jpeg')) {
|
||||
$headers = array("Referer: https://www.pixiv.net/member_illust.php?mode=medium&illust_id=" . $illustId);
|
||||
$illust = getContents($url, $headers);
|
||||
if(strpos($illust, "404 Not Found") !== false) {
|
||||
$illust = getContents(str_replace("jpg", "png", $url), $headers);
|
||||
}
|
||||
file_put_contents($path . '/' . $illustId . '.jpeg', $illust);
|
||||
}
|
||||
|
||||
return 'cache/pixiv_img/' . $illustId . ".jpeg";
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
class PlanetLibreBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'pit-fgfjiudghdf';
|
||||
const NAME = 'PlanetLibre';
|
||||
const URI = 'http://www.planet-libre.org';
|
||||
const DESCRIPTION = 'Returns the 5 newest posts from PlanetLibre (full text)';
|
||||
|
||||
private function extractContent($url){
|
||||
$html2 = getSimpleHTMLDOM($url);
|
||||
$text = $html2->find('div[class="post-text"]', 0)->innertext;
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request PlanetLibre.');
|
||||
$limit = 0;
|
||||
foreach($html->find('div.post') as $element) {
|
||||
if($limit < 5) {
|
||||
$item = array();
|
||||
$item['title'] = $element->find('h1', 0)->plaintext;
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
$item['timestamp'] = strtotime(
|
||||
str_replace(
|
||||
'/',
|
||||
'-',
|
||||
$element->find('div[class="post-date"]', 0)->plaintext
|
||||
)
|
||||
);
|
||||
|
||||
$item['content'] = $this->extractContent($item['uri']);
|
||||
$this->items[] = $item;
|
||||
$limit++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
bridges/RadioMelodieBridge.php
Normal file
30
bridges/RadioMelodieBridge.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
class RadioMelodieBridge extends BridgeAbstract {
|
||||
const NAME = 'Radio Melodie Actu';
|
||||
const URI = 'https://www.radiomelodie.com/';
|
||||
const DESCRIPTION = 'Retourne les actualités publiées par Radio Melodie';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . 'actu')
|
||||
or returnServerError('Could not request Radio Melodie.');
|
||||
$list = $html->find('div[class=actuitem]');
|
||||
foreach($list as $element) {
|
||||
$item = array();
|
||||
|
||||
// Get picture URL
|
||||
$pictureHTML = $element->find('div[class=picture]');
|
||||
preg_match(
|
||||
'/background-image:url\((.*)\);/',
|
||||
$pictureHTML[0]->getAttribute('style'),
|
||||
$pictures);
|
||||
$pictureURL = $pictures[1];
|
||||
|
||||
$item['enclosures'] = array($pictureURL);
|
||||
$item['uri'] = self::URI . $element->parent()->href;
|
||||
$item['title'] = $element->find('h3', 0)->plaintext;
|
||||
$item['content'] = $element->find('p', 0)->plaintext . '<br/><img src="'.$pictureURL.'"/>';
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -14,7 +14,7 @@ class SoundCloudBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
const CLIENT_ID = '0aca19eae3843844e4053c6d8fdb7875';
|
||||
const CLIENT_ID = '4jkoEFmZEDaqjwJ9Eih4ATNhcH3vMVfp';
|
||||
|
||||
public function collectData(){
|
||||
|
||||
|
@@ -2,9 +2,9 @@
|
||||
class SteamBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Steam Bridge';
|
||||
const URI = 'https://steamcommunity.com/';
|
||||
const URI = 'https://store.steampowered.com/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Returns games list';
|
||||
const DESCRIPTION = 'Returns apps list';
|
||||
const MAINTAINER = 'jacknumber';
|
||||
const PARAMETERS = array(
|
||||
'Wishlist' => array(
|
||||
@@ -47,16 +47,6 @@ class SteamBridge extends BridgeAbstract {
|
||||
'AED' => 'ae',
|
||||
),
|
||||
),
|
||||
'sort' => array(
|
||||
'name' => 'Sort by',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Rank' => 'rank',
|
||||
'Date Added' => 'added',
|
||||
'Name' => 'name',
|
||||
'Price' => 'price',
|
||||
)
|
||||
),
|
||||
'only_discount' => array(
|
||||
'name' => 'Only discount',
|
||||
'type' => 'checkbox',
|
||||
@@ -68,62 +58,100 @@ class SteamBridge extends BridgeAbstract {
|
||||
|
||||
$username = $this->getInput('username');
|
||||
$params = array(
|
||||
'sort' => $this->getInput('sort'),
|
||||
'cc' => $this->getInput('currency')
|
||||
);
|
||||
|
||||
$url = self::URI . 'id/' . $username . '/wishlist?' . http_build_query($params);
|
||||
$url = self::URI . 'wishlist/id/' . $username . '?' . http_build_query($params);
|
||||
|
||||
$targetVariable = 'g_rgAppInfo';
|
||||
$sort = array();
|
||||
|
||||
$html = '';
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError("Could not request Steam Wishlist. Tried:\n - $url");
|
||||
|
||||
foreach($html->find('#wishlist_items .wishlistRow') as $element) {
|
||||
$jsContent = $html->find('.responsive_page_template_content script', 0)->innertext;
|
||||
|
||||
$gameTitle = $element->find('h4', 0)->plaintext;
|
||||
$gameUri = $element->find('.storepage_btn_ctn a', 0)->href;
|
||||
$gameImg = $element->find('.gameListRowLogo img', 0)->src;
|
||||
if(preg_match('/var ' . $targetVariable . ' = (.*?);/s', $jsContent, $matches)) {
|
||||
$appsData = json_decode($matches[1]);
|
||||
} else {
|
||||
returnServerError("Could not parse JS variable ($targetVariable) in page content.");
|
||||
}
|
||||
|
||||
$discountBlock = $element->find('.discount_block', 0);
|
||||
foreach($appsData as $id => $element) {
|
||||
|
||||
$appType = $element->type;
|
||||
$appIsBuyable = 0;
|
||||
$appHasDiscount = 0;
|
||||
$appIsFree = 0;
|
||||
|
||||
if($element->subs) {
|
||||
$appIsBuyable = 1;
|
||||
|
||||
if($element->subs[0]->discount_pct) {
|
||||
|
||||
$appHasDiscount = 1;
|
||||
$discountBlock = str_get_html($element->subs[0]->discount_block);
|
||||
$appDiscountValue = $discountBlock->find('.discount_pct', 0)->plaintext;
|
||||
$appOldPrice = $discountBlock->find('.discount_original_price', 0)->plaintext;
|
||||
$appNewPrice = $discountBlock->find('.discount_final_price', 0)->plaintext;
|
||||
$appPrice = $appNewPrice;
|
||||
|
||||
} else {
|
||||
|
||||
if($this->getInput('only_discount')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$appPrice = $element->subs[0]->price / 100;
|
||||
}
|
||||
|
||||
if($element->find('.discount_block', 0)) {
|
||||
$gameHasPromo = 1;
|
||||
} else {
|
||||
|
||||
if($this->getInput('only_discount')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$gameHasPromo = 0;
|
||||
|
||||
}
|
||||
|
||||
if($gameHasPromo) {
|
||||
|
||||
$gamePromoValue = $discountBlock->find('.discount_pct', 0)->plaintext;
|
||||
$gameOldPrice = $discountBlock->find('.discount_original_price', 0)->plaintext;
|
||||
$gameNewPrice = $discountBlock->find('.discount_final_price', 0)->plaintext;
|
||||
$gamePrice = $gameNewPrice;
|
||||
|
||||
} else {
|
||||
$gamePrice = $element->find('.gameListPriceData .price', 0)->plaintext;
|
||||
if(isset($element->free) && $element->free = 1) {
|
||||
$appIsFree = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $gameUri;
|
||||
$item['title'] = $gameTitle;
|
||||
$item['price'] = $gamePrice;
|
||||
$item['hasPromo'] = $gameHasPromo;
|
||||
$item['uri'] = "http://store.steampowered.com/app/$id/";
|
||||
$item['title'] = $element->name;
|
||||
$item['type'] = $appType;
|
||||
$item['cover'] = str_replace('_292x136', '', $element->capsule);
|
||||
$item['timestamp'] = $element->added;
|
||||
$item['isBuyable'] = $appIsBuyable;
|
||||
$item['hasDiscount'] = $appHasDiscount;
|
||||
$item['isFree'] = $appIsFree;
|
||||
$item['priority'] = $element->priority;
|
||||
|
||||
if($gameHasPromo) {
|
||||
if($appIsBuyable) {
|
||||
$item['price'] = floatval(str_replace(',', '.', $appPrice));
|
||||
}
|
||||
|
||||
$item['promoValue'] = $gamePromoValue;
|
||||
$item['oldPrice'] = $gameOldPrice;
|
||||
$item['newPrice'] = $gameNewPrice;
|
||||
if($appHasDiscount) {
|
||||
|
||||
$item['discount']['value'] = $appDiscountValue;
|
||||
$item['discount']['oldPrice'] = floatval(str_replace(',', '.', $appOldPrice));
|
||||
$item['discount']['newPrice'] = floatval(str_replace(',', '.', $appNewPrice));
|
||||
|
||||
}
|
||||
|
||||
$item['enclosures'] = array();
|
||||
$item['enclosures'][] = str_replace('_292x136', '', $element->capsule);
|
||||
|
||||
foreach($element->screenshots as $screenshot) {
|
||||
$item['enclosures'][] = substr($element->capsule, 0, -31) . $screenshot;
|
||||
}
|
||||
|
||||
$sort[$id] = $element->priority;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
array_multisort($sort, SORT_ASC, $this->items);
|
||||
}
|
||||
}
|
||||
|
57
bridges/SupInfoBridge.php
Normal file
57
bridges/SupInfoBridge.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
class SupInfoBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'SupInfoBridge';
|
||||
const URI = 'https://www.supinfo.com';
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
'tag' => array(
|
||||
'name' => 'Category (not mandatory)',
|
||||
'type' => 'text',
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData() {
|
||||
|
||||
if(empty($this->getInput('tag'))) {
|
||||
$html = getSimpleHTMLDOM(self::URI . '/articles/')
|
||||
or returnServerError('Unable to fetch articles !');
|
||||
} else {
|
||||
$html = getSimpleHTMLDOM(self::URI . '/articles/tag/' . $this->getInput('tag'))
|
||||
or returnServerError('Unable to fetch articles !');
|
||||
}
|
||||
$content = $html->find('#latest', 0)->find('ul[class=courseContent]', 0);
|
||||
|
||||
for($i = 0; $i < 5; $i++) {
|
||||
|
||||
$this->items[] = $this->fetchArticle($content->find('h4', $i)->find('a', 0)->href);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function fetchArticle($link) {
|
||||
|
||||
$articleHTML = getSimpleHTMLDOM(self::URI . $link)
|
||||
or returnServerError('Unable to fetch article !');
|
||||
|
||||
$article = $articleHTML->find('div[id=courseDocZero]', 0);
|
||||
$item = array();
|
||||
$item['author'] = $article->find('#courseMetas', 0)->find('a', 0)->plaintext;
|
||||
$item['id'] = $link;
|
||||
$item['uri'] = self::URI . $link;
|
||||
$item['title'] = $article->find('h1', 0)->plaintext;
|
||||
$date = explode(' ', $article->find('#courseMetas', 0)->find('span', 1)->plaintext);
|
||||
$item['timestamp'] = DateTime::createFromFormat('d/m/Y H:i:s', $date[2] . ' ' . $date[4])->getTimestamp();
|
||||
|
||||
$article->find('div[id=courseHeader]', 0)->innertext = '';
|
||||
$article->find('div[id=author-infos]', 0)->innertext = '';
|
||||
$article->find('div[id=cartouche-tete]', 0)->innertext = '';
|
||||
$item['content'] = $article;
|
||||
|
||||
return $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -1,96 +0,0 @@
|
||||
<?php
|
||||
class T411Bridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'T411 Bridge';
|
||||
const URI = 'https://www.t411.al/';
|
||||
const DESCRIPTION = 'Returns the 10 newest torrents with specified search
|
||||
terms <br /> Use url part after "?" mark when using their search engine.';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'search' => array(
|
||||
'name' => 'Search criteria',
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
|
||||
//Utility function for retrieving text based on start and end delimiters
|
||||
function extractFromDelimiters($string, $start, $end){
|
||||
if(strpos($string, $start) !== false) {
|
||||
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
|
||||
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
|
||||
return $section_retrieved;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Retrieve torrent listing from search results, which does not contain torrent description
|
||||
$url = self::URI
|
||||
. 'torrents/search/?search='
|
||||
. urlencode($this->getInput('search'))
|
||||
. '&order=added&type=desc';
|
||||
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request t411: ' . $url);
|
||||
|
||||
$results = $html->find('table.results', 0);
|
||||
if (is_null($results))
|
||||
returnServerError('No results from t411: ' . $url);
|
||||
$limit = 0;
|
||||
|
||||
//Process each item individually
|
||||
foreach($results->find('tr') as $element) {
|
||||
|
||||
//Limit total amount of requests and ignore table header
|
||||
if($limit >= 10) {
|
||||
break;
|
||||
}
|
||||
if(is_object($element->find('th', 0))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Requests are rate-limited
|
||||
usleep(500000); //So we need to wait (500ms)
|
||||
|
||||
//Retrieve data from RSS entry
|
||||
$item_uri = self::URI
|
||||
. 'torrents/details/?id='
|
||||
. extractFromDelimiters($element->find('a.nfo', 0)->outertext, '?id=', '"');
|
||||
|
||||
$item_title = extractFromDelimiters($element->outertext, '" title="', '"');
|
||||
$item_date = strtotime($element->find('dd', 0)->plaintext);
|
||||
|
||||
//Retrieve full description from torrent page
|
||||
$item_html = getSimpleHTMLDOM($item_uri);
|
||||
if(!$item_html) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Retrieve data from page contents
|
||||
$item_desc = $item_html->find('div.description', 0);
|
||||
$item_author = $item_html->find('a.profile', 0)->innertext;
|
||||
|
||||
//Cleanup advertisments
|
||||
$divs = explode('<div class="align-center">', $item_desc->innertext);
|
||||
$item_desc = '';
|
||||
foreach ($divs as $text)
|
||||
if (strpos($text, 'adprovider.adlure.net') === false)
|
||||
$item_desc = $item_desc . '<div class="align-center">' . $text;
|
||||
|
||||
$item_desc = preg_replace('/<h2 class="align-center">LIENS DE T..?L..?CHARGEMENT<\/h2>/i', '', $item_desc);
|
||||
|
||||
//Build and add final item
|
||||
$item = array();
|
||||
$item['uri'] = $item_uri;
|
||||
$item['title'] = $item_title;
|
||||
$item['author'] = $item_author;
|
||||
$item['timestamp'] = $item_date;
|
||||
$item['content'] = $item_desc;
|
||||
$this->items[] = $item;
|
||||
$limit++;
|
||||
}
|
||||
}
|
||||
}
|
38
bridges/TebeoBridge.php
Normal file
38
bridges/TebeoBridge.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
class TebeoBridge extends FeedExpander {
|
||||
const NAME = 'Tébéo Bridge';
|
||||
const URI = 'http://www.tebeo.bzh/';
|
||||
const CACHE_TIMEOUT = 21600; //6h
|
||||
const DESCRIPTION = 'Returns the newest Tébéo videos by category';
|
||||
const MAINTAINER = 'Mitsukarenai';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'cat' => array(
|
||||
'name' => 'Catégorie',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Toutes les vidéos' => '/',
|
||||
'Actualité' => '/14-actualite',
|
||||
'Sport' => '/3-sport',
|
||||
'Culture-Loisirs' => '/5-culture-loisirs',
|
||||
'Société' => '/15-societe',
|
||||
'Langue Bretonne' => '/9-langue-bretonne'
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$url = self::URI . '/le-replay/' . $this->getInput('cat');
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request Tébéo.');
|
||||
|
||||
foreach($html->find('div[id=items_replay] div.replay') as $element) {
|
||||
$item = array();
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
$item['title'] = $element->find('h3', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($element->find('p.moment-format-day', 0)->plaintext);
|
||||
$item['content'] = '<a href="'.$item['uri'].'"><img alt="" src="'.$element->find('img', 0)->src.'"></a>';
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,7 +11,7 @@ class ThePirateBayBridge extends BridgeAbstract {
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'q' => array(
|
||||
'name' => 'keywords, separated by semicolons',
|
||||
'name' => 'keywords/username/category, separated by semicolons',
|
||||
'exampleValue' => 'first list;second list;…',
|
||||
'required' => true
|
||||
),
|
||||
@@ -24,9 +24,9 @@ class ThePirateBayBridge extends BridgeAbstract {
|
||||
'user' => 'usr'
|
||||
)
|
||||
),
|
||||
'cat_check' => array(
|
||||
'catCheck' => array(
|
||||
'type' => 'checkbox',
|
||||
'name' => 'Specify category for normal search ?',
|
||||
'name' => 'Specify category for keyword search ?',
|
||||
),
|
||||
'cat' => array(
|
||||
'name' => 'Category number',
|
||||
@@ -94,7 +94,7 @@ class ThePirateBayBridge extends BridgeAbstract {
|
||||
return $timestamp;
|
||||
}
|
||||
|
||||
$catBool = $this->getInput('cat_check');
|
||||
$catBool = $this->getInput('catCheck');
|
||||
if($catBool) {
|
||||
$catNum = $this->getInput('cat');
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ class Torrent9Bridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'lagaisse';
|
||||
const NAME = 'Torrent9 Bridge';
|
||||
const URI = 'http://www.torrent9.biz';
|
||||
const URI = 'http://www.torrent9.pe';
|
||||
const CACHE_TIMEOUT = 86400; // 24h = 86400s
|
||||
const DESCRIPTION = 'Returns latest torrents';
|
||||
|
||||
|
@@ -44,6 +44,25 @@ class TwitterBridge extends BridgeAbstract {
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Hide retweets'
|
||||
)
|
||||
),
|
||||
'By list' => array(
|
||||
'user' => array(
|
||||
'name' => 'User',
|
||||
'required' => true,
|
||||
'exampleValue' => 'sebsauvage',
|
||||
'title' => 'Insert a user name'
|
||||
),
|
||||
'list' => array(
|
||||
'name' => 'List',
|
||||
'required' => true,
|
||||
'title' => 'Insert the list name'
|
||||
),
|
||||
'filter' => array(
|
||||
'name' => 'Filter',
|
||||
'exampleValue' => '#rss-bridge',
|
||||
'required' => false,
|
||||
'title' => 'Specify term to search for'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -57,6 +76,8 @@ class TwitterBridge extends BridgeAbstract {
|
||||
$specific = '@';
|
||||
$param = 'u';
|
||||
break;
|
||||
case 'By list':
|
||||
return $this->getInput('list') . ' - Twitter list by ' . $this->getInput('user');
|
||||
default: return parent::getName();
|
||||
}
|
||||
return 'Twitter ' . $specific . $this->getInput($param);
|
||||
@@ -74,6 +95,11 @@ class TwitterBridge extends BridgeAbstract {
|
||||
. urlencode($this->getInput('u'));
|
||||
// Always return without replies!
|
||||
// . ($this->getInput('norep') ? '' : '/with_replies');
|
||||
case 'By list':
|
||||
return self::URI
|
||||
. urlencode($this->getInput('user'))
|
||||
. '/lists/'
|
||||
. str_replace(' ', '-', strtolower($this->getInput('list')));
|
||||
default: return parent::getURI();
|
||||
}
|
||||
}
|
||||
@@ -88,6 +114,8 @@ class TwitterBridge extends BridgeAbstract {
|
||||
returnServerError('No results for this query.');
|
||||
case 'By username':
|
||||
returnServerError('Requested username can\'t be found.');
|
||||
case 'By list':
|
||||
returnServerError('Requested username or list can\'t be found');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +160,18 @@ class TwitterBridge extends BridgeAbstract {
|
||||
// generate the title
|
||||
$item['title'] = strip_tags($this->fixAnchorSpacing($tweet->find('p.js-tweet-text', 0), '<a>'));
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'By list':
|
||||
// Check if filter applies to list (using raw content)
|
||||
if($this->getInput('filter')) {
|
||||
if(stripos($tweet->find('p.js-tweet-text', 0)->plaintext, $this->getInput('filter')) === false) {
|
||||
continue 2; // switch + for-loop!
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
$this->processContentLinks($tweet);
|
||||
$this->processEmojis($tweet);
|
||||
|
||||
|
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
class VineBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'ckiw';
|
||||
const NAME = 'Vine bridge';
|
||||
const URI = 'http://vine.co/';
|
||||
const DESCRIPTION = 'Returns the latests vines from vine user page';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'u' => array(
|
||||
'name' => 'User id',
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$uri = self::URI . '/u/' . $this->getInput('u') . '?mode=list';
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
foreach($html->find('.post') as $element) {
|
||||
$a = $element->find('a', 0);
|
||||
$a->href = str_replace('https://', 'http://', $a->href);
|
||||
$time = strtotime(ltrim($element->find('p', 0)->plaintext, ' Uploaded at '));
|
||||
$video = $element->find('video', 0);
|
||||
$video->controls = 'true';
|
||||
$element->find('h2', 0)->outertext = '';
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $a->href;
|
||||
$item['timestamp'] = $time;
|
||||
$item['title'] = $a->plaintext;
|
||||
$item['content'] = $element;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
class VkBridge extends BridgeAbstract {
|
||||
|
||||
class VkBridge extends BridgeAbstract
|
||||
{
|
||||
|
||||
const MAINTAINER = 'ahiles3005';
|
||||
const NAME = 'VK.com';
|
||||
const URI = 'http://vk.com/';
|
||||
const URI = 'https://vk.com/';
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const DESCRIPTION = 'Working with open pages';
|
||||
const PARAMETERS = array(
|
||||
@@ -15,52 +17,323 @@ class VkBridge extends BridgeAbstract {
|
||||
)
|
||||
);
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
protected $pageName;
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
if (!is_null($this->getInput('u'))) {
|
||||
return static::URI . urlencode($this->getInput('u'));
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
public function getName()
|
||||
{
|
||||
if ($this->pageName) {
|
||||
return $this->pageName;
|
||||
}
|
||||
|
||||
ini_set('user-agent', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0');
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
$text_html = getContents($this->getURI())
|
||||
or returnServerError('No results for group or user name "' . $this->getInput('u') . '".');
|
||||
public function collectData()
|
||||
{
|
||||
$text_html = $this->getContents()
|
||||
or returnServerError('No results for group or user name "' . $this->getInput('u') . '".');
|
||||
|
||||
$text_html = iconv('windows-1251', 'utf-8', $text_html);
|
||||
// makes album link generating work correctly
|
||||
$text_html = str_replace('"class="page_album_link">', '" class="page_album_link">', $text_html);
|
||||
$html = str_get_html($text_html);
|
||||
$pageName = $html->find('.page_name', 0);
|
||||
if (is_object($pageName)) {
|
||||
$pageName = $pageName->plaintext;
|
||||
$this->pageName = htmlspecialchars_decode($pageName);
|
||||
}
|
||||
$pinned_post_item = null;
|
||||
$last_post_id = 0;
|
||||
|
||||
foreach($html->find('.post') as $post) {
|
||||
foreach ($html->find('.post') as $post) {
|
||||
|
||||
if(is_object($post->find('a.wall_post_more', 0))) {
|
||||
$is_pinned_post = false;
|
||||
if (strpos($post->getAttribute('class'), 'post_fixed') !== false) {
|
||||
$is_pinned_post = true;
|
||||
}
|
||||
|
||||
if (is_object($post->find('a.wall_post_more', 0))) {
|
||||
//delete link "show full" in content
|
||||
$post->find('a.wall_post_more', 0)->outertext = '';
|
||||
}
|
||||
|
||||
$content_suffix = "";
|
||||
|
||||
// looking for external links
|
||||
$external_link_selectors = array(
|
||||
'a.page_media_link_title',
|
||||
'div.page_media_link_title > a',
|
||||
'div.media_desc > a.lnk',
|
||||
);
|
||||
|
||||
foreach($external_link_selectors as $sel) {
|
||||
if (is_object($post->find($sel, 0))) {
|
||||
$a = $post->find($sel, 0);
|
||||
$innertext = $a->innertext;
|
||||
$parsed_url = parse_url($a->getAttribute('href'));
|
||||
if (strpos($parsed_url['path'], '/away.php') !== 0) continue;
|
||||
parse_str($parsed_url["query"], $parsed_query);
|
||||
$content_suffix .= "<br>External link: <a href='" . $parsed_query["to"] . "'>$innertext</a>";
|
||||
}
|
||||
}
|
||||
|
||||
// remove external link from content
|
||||
$external_link_selectors_to_remove = array(
|
||||
'div.page_media_thumbed_link',
|
||||
'div.page_media_link_desc_wrap',
|
||||
'div.media_desc > a.lnk',
|
||||
);
|
||||
|
||||
foreach($external_link_selectors_to_remove as $sel) {
|
||||
if (is_object($post->find($sel, 0))) {
|
||||
$post->find($sel, 0)->outertext = '';
|
||||
}
|
||||
}
|
||||
|
||||
// looking for article
|
||||
$article = $post->find("a.article_snippet", 0);
|
||||
if (is_object($article)) {
|
||||
if (strpos($article->getAttribute('class'), "article_snippet_mini") !== false) {
|
||||
$article_title_selector = "div.article_snippet_mini_title";
|
||||
$article_author_selector = "div.article_snippet_mini_info > .mem_link,
|
||||
div.article_snippet_mini_info > .group_link";
|
||||
$article_thumb_selector = "div.article_snippet_mini_thumb";
|
||||
} else {
|
||||
$article_title_selector = "div.article_snippet__title";
|
||||
$article_author_selector = "div.article_snippet__author";
|
||||
$article_thumb_selector = "div.article_snippet__image";
|
||||
}
|
||||
$article_title = $article->find($article_title_selector, 0)->innertext;
|
||||
$article_author = $article->find($article_author_selector, 0)->innertext;
|
||||
$article_link = self::URI . ltrim($article->getAttribute('href'), '/');
|
||||
$article_img_element_style = $article->find($article_thumb_selector, 0)->getAttribute('style');
|
||||
preg_match('/background-image: url\((.*)\)/', $article_img_element_style, $matches);
|
||||
if (count($matches) > 0) {
|
||||
$content_suffix .= "<br><img src='" . $matches[1] . "'>";
|
||||
}
|
||||
$content_suffix .= "<br>Article: <a href='$article_link'>$article_title ($article_author)</a>";
|
||||
$article->outertext = '';
|
||||
}
|
||||
|
||||
// get video on post
|
||||
$video = $post->find('div.post_video_desc', 0);
|
||||
if (is_object($video)) {
|
||||
$video_title = $video->find('div.post_video_title', 0)->plaintext;
|
||||
$video_link = self::URI . ltrim( $video->find('a.lnk', 0)->getAttribute('href'), '/' );
|
||||
$content_suffix .= "<br>Video: <a href='$video_link'>$video_title</a>";
|
||||
$video->outertext = '';
|
||||
}
|
||||
|
||||
// get all other videos
|
||||
foreach($post->find('a.page_post_thumb_video') as $a) {
|
||||
$video_title = $a->getAttribute('aria-label');
|
||||
$temp = explode(" ", $video_title, 2);
|
||||
if (count($temp) > 1) $video_title = $temp[1];
|
||||
$video_link = self::URI . ltrim( $a->getAttribute('href'), '/' );
|
||||
$content_suffix .= "<br>Video: <a href='$video_link'>$video_title</a>";
|
||||
$a->outertext = '';
|
||||
}
|
||||
|
||||
// get all photos
|
||||
foreach($post->find('div.wall_text > a.page_post_thumb_wrap') as $a) {
|
||||
$result = $this->getPhoto($a);
|
||||
if ($result == null) continue;
|
||||
$a->outertext = '';
|
||||
$content_suffix .= "<br>$result";
|
||||
}
|
||||
|
||||
// get albums
|
||||
foreach($post->find('.page_album_wrap') as $el) {
|
||||
$a = $el->find('.page_album_link', 0);
|
||||
$album_title = $a->find('.page_album_title_text', 0)->getAttribute('title');
|
||||
$album_link = self::URI . ltrim($a->getAttribute('href'), '/');
|
||||
$el->outertext = '';
|
||||
$content_suffix .= "<br>Album: <a href='$album_link'>$album_title</a>";
|
||||
}
|
||||
|
||||
// get photo documents
|
||||
foreach($post->find('a.page_doc_photo_href') as $a) {
|
||||
$doc_link = self::URI . ltrim($a->getAttribute('href'), '/');
|
||||
$doc_gif_label_element = $a->find(".page_gif_label", 0);
|
||||
$doc_title_element = $a->find(".doc_label", 0);
|
||||
|
||||
if (is_object($doc_gif_label_element)) {
|
||||
$gif_preview_img = backgroundToImg($a->find('.page_doc_photo', 0));
|
||||
$content_suffix .= "<br>Gif: <a href='$doc_link'>$gif_preview_img</a>";
|
||||
|
||||
} else if (is_object($doc_title_element)) {
|
||||
$doc_title = $doc_title_element->innertext;
|
||||
$content_suffix .= "<br>Doc: <a href='$doc_link'>$doc_title</a>";
|
||||
|
||||
} else {
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
$a->outertext = '';
|
||||
}
|
||||
|
||||
// get other documents
|
||||
foreach($post->find('div.page_doc_row') as $div) {
|
||||
$doc_title_element = $div->find("a.page_doc_title", 0);
|
||||
|
||||
if (is_object($doc_title_element)) {
|
||||
$doc_title = $doc_title_element->innertext;
|
||||
$doc_link = self::URI . ltrim($doc_title_element->getAttribute('href'), '/');
|
||||
$content_suffix .= "<br>Doc: <a href='$doc_link'>$doc_title</a>";
|
||||
|
||||
} else {
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
$div->outertext = '';
|
||||
}
|
||||
|
||||
// get polls
|
||||
foreach($post->find('div.page_media_poll_wrap') as $div) {
|
||||
$poll_title = $div->find('.page_media_poll_title', 0)->innertext;
|
||||
$content_suffix .= "<br>Poll: $poll_title";
|
||||
foreach($div->find('div.page_poll_text') as $poll_stat_title) {
|
||||
$content_suffix .= "<br>- " . $poll_stat_title->innertext;
|
||||
}
|
||||
$div->outertext = '';
|
||||
}
|
||||
|
||||
// get sign
|
||||
$post_author = $pageName;
|
||||
foreach($post->find('a.wall_signed_by') as $a) {
|
||||
$post_author = $a->innertext;
|
||||
$a->outertext = '';
|
||||
}
|
||||
|
||||
if (is_object($post->find('div.copy_quote', 0))) {
|
||||
$copy_quote = $post->find('div.copy_quote', 0);
|
||||
if ($copy_post_header = $copy_quote->find('div.copy_post_header', 0)) {
|
||||
$copy_post_header->outertext = '';
|
||||
}
|
||||
$copy_quote_content = $copy_quote->innertext;
|
||||
$copy_quote->outertext = "<br>Reposted: <br>$copy_quote_content";
|
||||
}
|
||||
|
||||
$item = array();
|
||||
$item['content'] = strip_tags(backgroundToImg($post->find('div.wall_text', 0)->innertext), '<br><img>');
|
||||
if(is_object($post->find('a.page_media_link_title', 0))) {
|
||||
$link = $post->find('a.page_media_link_title', 0)->getAttribute('href');
|
||||
|
||||
//external link in the post
|
||||
$item['content'] .= "\n\rExternal link: "
|
||||
. str_replace('/away.php?to=', '', urldecode($link));
|
||||
}
|
||||
|
||||
//get video on post
|
||||
if(is_object($post->find('span.post_video_title_content', 0))) {
|
||||
$titleVideo = $post->find('span.post_video_title_content', 0)->plaintext;
|
||||
$linkToVideo = self::URI . $post->find('a.page_post_thumb_video', 0)->getAttribute('href');
|
||||
$item['content'] .= "\n\r {$titleVideo}: {$linkToVideo}";
|
||||
}
|
||||
$item['content'] .= $content_suffix;
|
||||
|
||||
// get post link
|
||||
$item['uri'] = self::URI . $post->find('a.post_link', 0)->getAttribute('href');
|
||||
$item['date'] = $post->find('span.rel_date', 0)->plaintext;
|
||||
$this->items[] = $item;
|
||||
// var_dump($item['date']);
|
||||
$post_link = $post->find('a.post_link', 0)->getAttribute('href');
|
||||
preg_match("/wall-?\d+_(\d+)/", $post_link, $preg_match_result);
|
||||
$item['post_id'] = intval($preg_match_result[1]);
|
||||
if (substr(self::URI, -1) == '/') {
|
||||
$post_link = self::URI . ltrim($post_link, "/");
|
||||
} else {
|
||||
$post_link = self::URI . $post_link;
|
||||
}
|
||||
$item['uri'] = $post_link;
|
||||
$item['timestamp'] = $this->getTime($post);
|
||||
$item['title'] = $this->getTitle($item['content']);
|
||||
$item['author'] = $post_author;
|
||||
if ($is_pinned_post) {
|
||||
// do not append it now
|
||||
$pinned_post_item = $item;
|
||||
} else {
|
||||
$last_post_id = $item['post_id'];
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (is_null($pinned_post_item)) {
|
||||
return;
|
||||
} else if (count($this->items) == 0) {
|
||||
$this->items[] = $pinned_post_item;
|
||||
} else if ($last_post_id < $pinned_post_item['post_id']) {
|
||||
$this->items[] = $pinned_post_item;
|
||||
usort($this->items, function ($item1, $item2) {
|
||||
return $item2['post_id'] - $item1['post_id'];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private function getPhoto($a) {
|
||||
$onclick = $a->getAttribute('onclick');
|
||||
preg_match('/return showPhoto\(.+?({.*})/', $onclick, $preg_match_result);
|
||||
if (count($preg_match_result) == 0) return;
|
||||
|
||||
$arg = htmlspecialchars_decode( str_replace('queue:1', '"queue":1', $preg_match_result[1]) );
|
||||
$data = json_decode($arg, true);
|
||||
if ($data == null) return;
|
||||
|
||||
$thumb = $data['temp']['base'] . $data['temp']['x_'][0] . ".jpg";
|
||||
$original = '';
|
||||
foreach(array('y_', 'z_', 'w_') as $key) {
|
||||
if (!isset($data['temp'][$key])) continue;
|
||||
if (!isset($data['temp'][$key][0])) continue;
|
||||
if (substr($data['temp'][$key][0], 0, 4) == "http") {
|
||||
$base = "";
|
||||
} else {
|
||||
$base = $data['temp']['base'];
|
||||
}
|
||||
$original = $base . $data['temp'][$key][0] . ".jpg";
|
||||
}
|
||||
|
||||
if ($original) {
|
||||
return "<a href='$original'><img src='$thumb'></a>";
|
||||
} else {
|
||||
return "<img src='$thumb'>";
|
||||
}
|
||||
}
|
||||
|
||||
private function getTitle($content)
|
||||
{
|
||||
preg_match('/^["\w\ \p{Cyrillic}\(\)\?#«»-]+/mu', htmlspecialchars_decode($content), $result);
|
||||
if (count($result) == 0) return "untitled";
|
||||
return $result[0];
|
||||
}
|
||||
|
||||
private function getTime($post)
|
||||
{
|
||||
if ($time = $post->find('span.rel_date', 0)->getAttribute('time')) {
|
||||
return $time;
|
||||
} else {
|
||||
$strdate = $post->find('span.rel_date', 0)->plaintext;
|
||||
|
||||
$date = date_parse($strdate);
|
||||
if (!$date['year']) {
|
||||
if (strstr($strdate, 'today') !== false) {
|
||||
$strdate = date('d-m-Y') . ' ' . $strdate;
|
||||
} elseif (strstr($strdate, 'yesterday ') !== false) {
|
||||
$time = time() - 60 * 60 * 24;
|
||||
$strdate = date('d-m-Y', $time) . ' ' . $strdate;
|
||||
} else {
|
||||
$strdate = $strdate . ' ' . date('Y');
|
||||
}
|
||||
|
||||
$date = date_parse($strdate);
|
||||
}
|
||||
return strtotime($date['day'] . '-' . $date['month'] . '-' . $date['year'] . ' ' .
|
||||
$date['hour'] . ':' . $date['minute']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getContents()
|
||||
{
|
||||
ini_set('user-agent', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0');
|
||||
|
||||
$header = array('Accept-language: en', 'Cookie: remixlang=3');
|
||||
|
||||
return getContents($this->getURI(), $header);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -1,16 +1,12 @@
|
||||
<?php
|
||||
class WorldOfTanksBridge extends BridgeAbstract {
|
||||
class WorldOfTanksBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
const MAINTAINER = 'Riduidel';
|
||||
const NAME = 'World of Tanks';
|
||||
const URI = 'http://worldoftanks.eu/';
|
||||
const DESCRIPTION = 'News about the tank slaughter game.';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'category' => array(
|
||||
// TODO: should be a list
|
||||
'name' => 'nom de la catégorie'
|
||||
),
|
||||
'lang' => array(
|
||||
'name' => 'Langue',
|
||||
'type' => 'list',
|
||||
@@ -26,47 +22,31 @@ class WorldOfTanksBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
private $title = '';
|
||||
public function collectData() {
|
||||
$this->collectExpandableDatas(sprintf('https://worldoftanks.eu/%s/rss/news/', $this->getInput('lang')));
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('lang'))) {
|
||||
$lang = $this->getInput('lang');
|
||||
$uri = self::URI . $lang . '/news/';
|
||||
if(!empty($this->getInput('category'))) {
|
||||
$uri .= 'pc-browser/' . $this->getInput('category') . '/';
|
||||
}
|
||||
return $uri;
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
$item['content'] = $this->loadFullArticle($item['uri']);
|
||||
return $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);
|
||||
|
||||
// Remove the scripts, please
|
||||
foreach($content->find('script') as $script) {
|
||||
$script->outertext = '';
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
return $this->title ?: parent::getName();
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
debugMessage("loaded HTML from " . $this->getURI());
|
||||
// customize name
|
||||
$this->title = $html->find('title', 0)->innertext;
|
||||
foreach($html->find('.b-imgblock_ico') as $infoLink) {
|
||||
$this->parseLine($infoLink);
|
||||
}
|
||||
}
|
||||
|
||||
private function parseLine($infoLink){
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $infoLink->href;
|
||||
// now load that uri from cache
|
||||
debugMessage('loading page ' . $item['uri']);
|
||||
$articlePage = getSimpleHTMLDOMCached($item['uri']);
|
||||
$content = $articlePage->find('.l-content', 0);
|
||||
defaultLinkTo($content, self::URI);
|
||||
$item['title'] = $content->find('h1', 0)->innertext;
|
||||
$item['content'] = $content->find('.b-content', 0)->innertext;
|
||||
$item['timestamp'] = $content->find('.b-statistic_time', 0)->getAttribute("data-timestamp");
|
||||
$this->items[] = $item;
|
||||
return $content->innertext;
|
||||
}
|
||||
}
|
||||
|
143
bridges/YGGTorrentBridge.php
Normal file
143
bridges/YGGTorrentBridge.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
/* This is a mashup of FlickrExploreBridge by sebsauvage and FlickrTagBridge
|
||||
* by erwang.providing the functionality of both in one.
|
||||
*/
|
||||
class YGGTorrentBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'Yggtorrent Bridge';
|
||||
const URI = 'https://yggtorrent.is';
|
||||
const DESCRIPTION = 'Returns torrent search from Yggtorrent';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
"cat" => array(
|
||||
"name" => "category",
|
||||
"type" => "list",
|
||||
"values" => array(
|
||||
"Toute les catégories" => "all.all",
|
||||
"Film/Vidéo - Toutes les sous-catégories" => "2145.all",
|
||||
"Film/Vidéo - Animation" => "2145.2178",
|
||||
"Film/Vidéo - Animation Série" => "2145.2179",
|
||||
"Film/Vidéo - Concert" => "2145.2180",
|
||||
"Film/Vidéo - Documentaire" => "2145.2181",
|
||||
"Film/Vidéo - Émission TV" => "2145.2182",
|
||||
"Film/Vidéo - Film" => "2145.2183",
|
||||
"Film/Vidéo - Série TV" => "2145.2184",
|
||||
"Film/Vidéo - Spectacle" => "2145.2185",
|
||||
"Film/Vidéo - Sport" => "2145.2186",
|
||||
"Film/Vidéo - Vidéo-clips" => "2145.2186",
|
||||
"Audio - Toutes les sous-catégories" => "2139.all",
|
||||
"Audio - Karaoké" => "2139.2147",
|
||||
"Audio - Musique" => "2139.2148",
|
||||
"Audio - Podcast Radio" => "2139.2150",
|
||||
"Audio - Samples" => "2139.2149",
|
||||
"Jeu vidéo - Toutes les sous-catégories" => "2142.all",
|
||||
"Jeu vidéo - Autre" => "2142.2167",
|
||||
"Jeu vidéo - Linux" => "2142.2159",
|
||||
"Jeu vidéo - MacOS" => "2142.2160",
|
||||
"Jeu vidéo - Microsoft" => "2142.2162",
|
||||
"Jeu vidéo - Nintendo" => "2142.2163",
|
||||
"Jeu vidéo - Smartphone" => "2142.2165",
|
||||
"Jeu vidéo - Sony" => "2142.2164",
|
||||
"Jeu vidéo - Tablette" => "2142.2166",
|
||||
"Jeu vidéo - Windows" => "2142.2161",
|
||||
"eBook - Toutes les sous-catégories" => "2140.all",
|
||||
"eBook - Audio" => "2140.2151",
|
||||
"eBook - Bds" => "2140.2152",
|
||||
"eBook - Comics" => "2140.2153",
|
||||
"eBook - Livres" => "2140.2154",
|
||||
"eBook - Mangas" => "2140.2155",
|
||||
"eBook - Presse" => "2140.2156",
|
||||
"Emulation - Toutes les sous-catégories" => "2141.all",
|
||||
"Emulation - Emulateurs" => "2141.2157",
|
||||
"Emulation - Roms" => "2141.2158",
|
||||
"GPS - Toutes les sous-catégories" => "2141.all",
|
||||
"GPS - Applications" => "2141.2168",
|
||||
"GPS - Cartes" => "2141.2169",
|
||||
"GPS - Divers" => "2141.2170"
|
||||
)
|
||||
),
|
||||
"nom" => array(
|
||||
"name" => "Nom",
|
||||
"description" => "Nom du torrent",
|
||||
"type" => "text"
|
||||
),
|
||||
"description" => array(
|
||||
"name" => "Description",
|
||||
"description" => "Description du torrent",
|
||||
"type" => "text"
|
||||
),
|
||||
"fichier" => array(
|
||||
"name" => "Fichier",
|
||||
"description" => "Fichier du torrent",
|
||||
"type" => "text"
|
||||
),
|
||||
"uploader" => array(
|
||||
"name" => "Uploader",
|
||||
"description" => "Uploader du torrent",
|
||||
"type" => "text"
|
||||
),
|
||||
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$catInfo = explode(".", $this->getInput("cat"));
|
||||
$category = $catInfo[0];
|
||||
$subcategory = $catInfo[1];
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . "/engine/search?name="
|
||||
. $this->getInput("nom")
|
||||
. "&description="
|
||||
. $this->getInput("description")
|
||||
. "&fichier="
|
||||
. $this->getInput("fichier")
|
||||
. "&file="
|
||||
. $this->getInput("uploader")
|
||||
. "&category="
|
||||
. $category
|
||||
. "&sub_category="
|
||||
. $subcategory
|
||||
. "&do=search")
|
||||
or returnServerError("Unable to query Yggtorrent !");
|
||||
|
||||
$count = 0;
|
||||
$results = $html->find(".results", 0);
|
||||
if(!$results) return;
|
||||
|
||||
foreach($results->find("tr") as $row) {
|
||||
$count++;
|
||||
if($count == 1) continue;
|
||||
if($count == 12) break;
|
||||
$item = array();
|
||||
$item["timestamp"] = $row->find(".hidden", 1)->plaintext;
|
||||
$item["title"] = $row->find("a", 1)->plaintext;
|
||||
$torrentData = $this->collectTorrentData($row->find("a", 1)->href);
|
||||
$item["author"] = $torrentData["author"];
|
||||
$item["content"] = $torrentData["content"];
|
||||
$item["seeders"] = $row->find("td", 7)->plaintext;
|
||||
$item["leechers"] = $row->find("td", 8)->plaintext;
|
||||
$item["size"] = $row->find("td", 5)->plaintext;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function collectTorrentData($url) {
|
||||
|
||||
//For weird reason, the link we get can be invalid, we fix it.
|
||||
$url_full = explode("/", $url);
|
||||
$url_full[4] = urlencode($url_full[4]);
|
||||
$url_full[5] = urlencode($url_full[5]);
|
||||
$url_full[6] = urlencode($url_full[6]);
|
||||
$url = implode("/", $url_full);
|
||||
$page = getSimpleHTMLDOM($url) or returnServerError("Unable to query Yggtorrent page !");
|
||||
$author = $page->find(".informations", 0)->find("a", 4)->plaintext;
|
||||
$content = $page->find(".default", 1);
|
||||
return array("author" => $author, "content" => $content);
|
||||
}
|
||||
}
|
@@ -50,11 +50,31 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
|
||||
private function ytBridgeQueryVideoInfo($vid, &$author, &$desc, &$time){
|
||||
$html = $this->ytGetSimpleHTMLDOM(self::URI . "watch?v=$vid");
|
||||
$author = $html->innertext;
|
||||
$author = substr($author, strpos($author, '"author=') + 8);
|
||||
$author = substr($author, 0, strpos($author, '\u0026'));
|
||||
$desc = $html->find('div#watch-description-text', 0)->innertext;
|
||||
$time = strtotime($html->find('meta[itemprop=datePublished]', 0)->getAttribute('content'));
|
||||
|
||||
// Skip unavailable videos
|
||||
if(!strpos($html->innertext, 'IS_UNAVAILABLE_PAGE')) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach($html->find('script') as $script) {
|
||||
$data = trim($script->innertext);
|
||||
|
||||
if(strpos($data, '{') !== 0)
|
||||
continue; // Wrong script
|
||||
|
||||
$json = json_decode($data);
|
||||
|
||||
if(!isset($json->itemListElement))
|
||||
continue; // Wrong script
|
||||
|
||||
$author = $json->itemListElement[0]->item->name;
|
||||
}
|
||||
|
||||
if(!is_null($html->find('#watch-description-text', 0)))
|
||||
$desc = $html->find('#watch-description-text', 0)->innertext;
|
||||
|
||||
if(!is_null($html->find('meta[itemprop=datePublished]', 0)))
|
||||
$time = strtotime($html->find('meta[itemprop=datePublished]', 0)->getAttribute('content'));
|
||||
}
|
||||
|
||||
private function ytBridgeAddItem($vid, $title, $author, $desc, $time){
|
||||
@@ -84,13 +104,14 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
|
||||
$vid = str_replace('yt:video:', '', $element->find('id', 0)->plaintext);
|
||||
$time = strtotime($element->find('published', 0)->plaintext);
|
||||
$this->ytBridgeAddItem($vid, $title, $author, $desc, $time);
|
||||
if(strpos($vid, 'googleads') === false)
|
||||
$this->ytBridgeAddItem($vid, $title, $author, $desc, $time);
|
||||
}
|
||||
$this->request = $this->ytBridgeFixTitle($xml->find('feed > title', 0)->plaintext);
|
||||
$this->feedName = $this->ytBridgeFixTitle($xml->find('feed > title', 0)->plaintext); // feedName will be used by getName()
|
||||
}
|
||||
|
||||
private function ytBridgeParseHtmlListing($html, $element_selector, $title_selector){
|
||||
$limit = 10;
|
||||
private function ytBridgeParseHtmlListing($html, $element_selector, $title_selector, $add_parsed_items = true) {
|
||||
$limit = $add_parsed_items ? 10 : INF;
|
||||
$count = 0;
|
||||
foreach($html->find($element_selector) as $element) {
|
||||
if($count < $limit) {
|
||||
@@ -98,14 +119,18 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
$desc = '';
|
||||
$time = 0;
|
||||
$vid = str_replace('/watch?v=', '', $element->find('a', 0)->href);
|
||||
$vid = substr($vid, 0, strpos($vid, '&') ?: strlen($vid));
|
||||
$title = $this->ytBridgeFixTitle($element->find($title_selector, 0)->plaintext);
|
||||
if($title != '[Private Video]') {
|
||||
$this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time);
|
||||
$this->ytBridgeAddItem($vid, $title, $author, $desc, $time);
|
||||
if($title != '[Private Video]' && strpos($vid, 'googleads') === false) {
|
||||
if ($add_parsed_items) {
|
||||
$this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time);
|
||||
$this->ytBridgeAddItem($vid, $title, $author, $desc, $time);
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function ytBridgeFixTitle($title) {
|
||||
@@ -115,10 +140,8 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
|
||||
private function ytGetSimpleHTMLDOM($url){
|
||||
return getSimpleHTMLDOM($url,
|
||||
$use_include_path = false,
|
||||
$context = null,
|
||||
$offset = 0,
|
||||
$maxLen = null,
|
||||
$header = array(),
|
||||
$opts = array(),
|
||||
$lowercase = true,
|
||||
$forceTagsClosed = true,
|
||||
$target_charset = DEFAULT_TARGET_CHARSET,
|
||||
@@ -154,11 +177,20 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
}
|
||||
} elseif($this->getInput('p')) { /* playlist mode */
|
||||
$this->request = $this->getInput('p');
|
||||
$url_feed = self::URI . 'feeds/videos.xml?playlist_id=' . urlencode($this->request);
|
||||
$url_listing = self::URI . 'playlist?list=' . urlencode($this->request);
|
||||
$html = $this->ytGetSimpleHTMLDOM($url_listing)
|
||||
or returnServerError("Could not request YouTube. Tried:\n - $url_listing");
|
||||
$this->ytBridgeParseHtmlListing($html, 'tr.pl-video', '.pl-video-title a');
|
||||
$this->request = 'Playlist: ' . str_replace(' - YouTube', '', $html->find('title', 0)->plaintext);
|
||||
$item_count = $this->ytBridgeParseHtmlListing($html, 'tr.pl-video', '.pl-video-title a', false);
|
||||
if ($item_count <= 15 && ($xml = $this->ytGetSimpleHTMLDOM($url_feed))) {
|
||||
$this->ytBridgeParseXmlFeed($xml);
|
||||
} else {
|
||||
$this->ytBridgeParseHtmlListing($html, 'tr.pl-video', '.pl-video-title a');
|
||||
}
|
||||
$this->feedName = 'Playlist: ' . str_replace(' - YouTube', '', $html->find('title', 0)->plaintext); // feedName will be used by getName()
|
||||
usort($this->items, function ($item1, $item2) {
|
||||
return $item2['timestamp'] - $item1['timestamp'];
|
||||
});
|
||||
} elseif($this->getInput('s')) { /* search mode */
|
||||
$this->request = $this->getInput('s');
|
||||
$page = 1;
|
||||
@@ -175,8 +207,8 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
$html = $this->ytGetSimpleHTMLDOM($url_listing)
|
||||
or returnServerError("Could not request YouTube. Tried:\n - $url_listing");
|
||||
|
||||
$this->ytBridgeParseHtmlListing($html, 'div.yt-lockup', 'h3');
|
||||
$this->request = 'Search: ' . str_replace(' - YouTube', '', $html->find('title', 0)->plaintext);
|
||||
$this->ytBridgeParseHtmlListing($html, 'div.yt-lockup', 'h3 > a');
|
||||
$this->feedName = 'Search: ' . str_replace(' - YouTube', '', $html->find('title', 0)->plaintext); // feedName will be used by getName()
|
||||
} else { /* no valid mode */
|
||||
returnClientError("You must either specify either:\n - YouTube
|
||||
username (?u=...)\n - Channel id (?c=...)\n - Playlist id (?p=...)\n - Search (?s=...)");
|
||||
@@ -184,6 +216,15 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
return (!empty($this->request) ? $this->request . ' - ' : '') . 'YouTube Bridge';
|
||||
}
|
||||
// Name depends on queriedContext:
|
||||
switch($this->queriedContext) {
|
||||
case 'By username':
|
||||
case 'By channel id':
|
||||
case 'By playlist Id':
|
||||
case 'Search result':
|
||||
return $this->feedName . ' - YouTube'; // We already know it's a bridge, right?
|
||||
default:
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,9 @@ class FileCache implements CacheInterface {
|
||||
protected $param;
|
||||
|
||||
public function loadData(){
|
||||
return unserialize(file_get_contents($this->getCacheFile()));
|
||||
if(file_exists($this->getCacheFile())) {
|
||||
return unserialize(file_get_contents($this->getCacheFile()));
|
||||
}
|
||||
}
|
||||
|
||||
public function saveData($datas){
|
||||
|
27
config.default.ini.php
Normal file
27
config.default.ini.php
Normal file
@@ -0,0 +1,27 @@
|
||||
; <?php exit; ?> DO NOT REMOVE THIS LINE
|
||||
|
||||
; This file contains the default settings for RSS-Bridge. Do not change this
|
||||
; file, it will be replaced on the next update of RSS-Bridge! You can specify
|
||||
; your own configuration in 'config.ini.php' (copy this file).
|
||||
|
||||
[cache]
|
||||
|
||||
; Allow users to specify custom timeout for specific requests.
|
||||
; true = enabled
|
||||
; false = disabled (default)
|
||||
custom_timeout = false
|
||||
|
||||
[proxy]
|
||||
|
||||
; Sets the proxy url (i.e. "tcp://192.168.0.0:32")
|
||||
; "" = Proxy disabled (default)
|
||||
url = ""
|
||||
|
||||
; Sets the proxy name that is shown on the bridge instead of the proxy url.
|
||||
; "" = Show proxy url
|
||||
name = "Hidden proxy name"
|
||||
|
||||
; Allow users to disable proxy usage for specific requests.
|
||||
; true = enabled
|
||||
; false = disabled (default)
|
||||
by_bridge = false
|
@@ -15,8 +15,8 @@ class AtomFormat extends FormatAbstract{
|
||||
|
||||
$extraInfos = $this->getExtraInfos();
|
||||
$title = $this->xml_encode($extraInfos['name']);
|
||||
$uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : 'https://github.com/sebsauvage/rss-bridge';
|
||||
$icon = $this->xml_encode('http://icons.better-idea.org/icon?url='. $uri .'&size=64');
|
||||
$uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : 'https://github.com/RSS-Bridge/rss-bridge';
|
||||
$icon = $this->xml_encode($uri .'/favicon.ico');
|
||||
$uri = $this->xml_encode($uri);
|
||||
|
||||
$entries = '';
|
||||
|
@@ -18,10 +18,10 @@ class MrssFormat extends FormatAbstract {
|
||||
if(!empty($extraInfos['uri'])) {
|
||||
$uri = $this->xml_encode($extraInfos['uri']);
|
||||
} else {
|
||||
$uri = 'https://github.com/sebsauvage/rss-bridge';
|
||||
$uri = 'https://github.com/RSS-Bridge/rss-bridge';
|
||||
}
|
||||
|
||||
$icon = $this->xml_encode('http://icons.better-idea.org/icon?url='. $uri .'&size=64');
|
||||
$icon = $this->xml_encode($uri .'/favicon.ico');
|
||||
|
||||
$items = '';
|
||||
foreach($this->getItems() as $item) {
|
||||
|
185
index.php
185
index.php
@@ -10,40 +10,78 @@ TODO :
|
||||
- implement header('X-Cached-Version: '.date(DATE_ATOM, filemtime($cachefile)));
|
||||
*/
|
||||
|
||||
if(!file_exists('config.default.ini.php'))
|
||||
die('The default configuration file "config.default.ini.php" is missing!');
|
||||
|
||||
$config = parse_ini_file('config.default.ini.php', true, INI_SCANNER_TYPED);
|
||||
|
||||
if(file_exists('config.ini.php')) {
|
||||
// Replace default configuration with custom settings
|
||||
foreach(parse_ini_file('config.ini.php', true, INI_SCANNER_TYPED) as $header => $section) {
|
||||
foreach($section as $key => $value) {
|
||||
// Skip unknown sections and keys
|
||||
if(array_key_exists($header, $config) && array_key_exists($key, $config[$header])) {
|
||||
$config[$header][$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!is_string($config['proxy']['url']))
|
||||
die('Parameter [proxy] => "url" is not a valid string! Please check "config.ini.php"!');
|
||||
|
||||
if(!empty($config['proxy']['url']))
|
||||
define('PROXY_URL', $config['proxy']['url']);
|
||||
|
||||
if(!is_bool($config['proxy']['by_bridge']))
|
||||
die('Parameter [proxy] => "by_bridge" is not a valid Boolean! Please check "config.ini.php"!');
|
||||
|
||||
define('PROXY_BYBRIDGE', $config['proxy']['by_bridge']);
|
||||
|
||||
if(!is_string($config['proxy']['name']))
|
||||
die('Parameter [proxy] => "name" is not a valid string! Please check "config.ini.php"!');
|
||||
|
||||
define('PROXY_NAME', $config['proxy']['name']);
|
||||
|
||||
if(!is_bool($config['cache']['custom_timeout']))
|
||||
die('Parameter [cache] => "custom_timeout" is not a valid Boolean! Please check "config.ini.php"!');
|
||||
|
||||
define('CUSTOM_CACHE_TIMEOUT', $config['cache']['custom_timeout']);
|
||||
|
||||
// Defines the minimum required PHP version for RSS-Bridge
|
||||
define('PHP_VERSION_REQUIRED', '5.6.0');
|
||||
|
||||
//define('PROXY_URL', 'tcp://192.168.0.0:28');
|
||||
// Set to true if you allow users to disable proxy usage for specific bridges
|
||||
define('PROXY_BYBRIDGE', false);
|
||||
// Comment this line or keep PROXY_NAME empty to display PROXY_URL instead
|
||||
define('PROXY_NAME', 'Hidden Proxy Name');
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
error_reporting(0);
|
||||
|
||||
// Specify directory for cached files (using FileCache)
|
||||
define('CACHE_DIR', __DIR__ . '/cache');
|
||||
|
||||
// Specify path for whitelist file
|
||||
define('WHITELIST_FILE', __DIR__ . '/whitelist.txt');
|
||||
|
||||
|
||||
/*
|
||||
Move the CLI arguments to the $_GET array, in order to be able to use
|
||||
rss-bridge from the command line
|
||||
*/
|
||||
parse_str(implode('&', array_slice($argv, 1)), $cliArgs);
|
||||
$params = array_merge($_GET, $cliArgs);
|
||||
|
||||
/*
|
||||
Create a file named 'DEBUG' for enabling debug mode.
|
||||
For further security, you may put whitelisted IP addresses
|
||||
in the 'DEBUG' file, one IP per line. Empty file allows anyone(!).
|
||||
Debugging allows displaying PHP error messages and bypasses the cache: this can allow a malicious
|
||||
client to retrieve data about your server and hammer a provider throught your rss-bridge instance.
|
||||
For further security, you may put whitelisted IP addresses in the file,
|
||||
one IP per line. Empty file allows anyone(!).
|
||||
Debugging allows displaying PHP error messages and bypasses the cache: this
|
||||
can allow a malicious client to retrieve data about your server and hammer
|
||||
a provider throught your rss-bridge instance.
|
||||
*/
|
||||
if(file_exists('DEBUG')) {
|
||||
$debug_enabled = true;
|
||||
$debug_whitelist = trim(file_get_contents('DEBUG'));
|
||||
if(strlen($debug_whitelist) > 0) {
|
||||
$debug_enabled = false;
|
||||
foreach(explode("\n", $debug_whitelist) as $allowed_ip) {
|
||||
if(trim($allowed_ip) === $_SERVER['REMOTE_ADDR']) {
|
||||
$debug_enabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$debug_enabled = empty($debug_whitelist)
|
||||
|| in_array($_SERVER['REMOTE_ADDR'], explode("\n", $debug_whitelist));
|
||||
|
||||
if($debug_enabled) {
|
||||
ini_set('display_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
@@ -64,10 +102,27 @@ if(!extension_loaded('openssl'))
|
||||
if(!extension_loaded('libxml'))
|
||||
die('"libxml" extension not loaded. Please check "php.ini"');
|
||||
|
||||
if(!extension_loaded('mbstring'))
|
||||
die('"mbstring" extension not loaded. Please check "php.ini"');
|
||||
|
||||
if(!extension_loaded('simplexml'))
|
||||
die('"simplexml" extension not loaded. Please check "php.ini"');
|
||||
|
||||
if(!extension_loaded('curl'))
|
||||
die('"curl" extension not loaded. Please check "php.ini"');
|
||||
|
||||
// configuration checks
|
||||
if(ini_get('allow_url_fopen') !== "1")
|
||||
die('"allow_url_fopen" is not set to "1". Please check "php.ini');
|
||||
|
||||
// Check cache folder permissions (write permissions required)
|
||||
if(!is_writable(CACHE_DIR))
|
||||
die('RSS-Bridge does not have write permissions for ' . CACHE_DIR . '!');
|
||||
|
||||
// Check whitelist file permissions (only in DEBUG mode)
|
||||
if(!file_exists(WHITELIST_FILE) && !is_writable(dirname(WHITELIST_FILE)))
|
||||
die('RSS-Bridge does not have write permissions for ' . WHITELIST_FILE . '!');
|
||||
|
||||
// FIXME : beta test UA spoofing, please report any blacklisting by PHP-fopen-unfriendly websites
|
||||
|
||||
$userAgent = 'Mozilla/5.0(X11; Linux x86_64; rv:30.0)';
|
||||
@@ -77,24 +132,23 @@ $userAgent .= '+https://github.com/RSS-Bridge/rss-bridge)';
|
||||
ini_set('user_agent', $userAgent);
|
||||
|
||||
// default whitelist
|
||||
$whitelist_file = './whitelist.txt';
|
||||
$whitelist_default = array(
|
||||
"BandcampBridge",
|
||||
"CryptomeBridge",
|
||||
"DansTonChatBridge",
|
||||
"DuckDuckGoBridge",
|
||||
"FacebookBridge",
|
||||
"FlickrExploreBridge",
|
||||
"GooglePlusPostBridge",
|
||||
"GoogleSearchBridge",
|
||||
"IdenticaBridge",
|
||||
"InstagramBridge",
|
||||
"OpenClassroomsBridge",
|
||||
"PinterestBridge",
|
||||
"ScmbBridge",
|
||||
"TwitterBridge",
|
||||
"WikipediaBridge",
|
||||
"YoutubeBridge");
|
||||
'BandcampBridge',
|
||||
'CryptomeBridge',
|
||||
'DansTonChatBridge',
|
||||
'DuckDuckGoBridge',
|
||||
'FacebookBridge',
|
||||
'FlickrExploreBridge',
|
||||
'GooglePlusPostBridge',
|
||||
'GoogleSearchBridge',
|
||||
'IdenticaBridge',
|
||||
'InstagramBridge',
|
||||
'OpenClassroomsBridge',
|
||||
'PinterestBridge',
|
||||
'ScmbBridge',
|
||||
'TwitterBridge',
|
||||
'WikipediaBridge',
|
||||
'YoutubeBridge');
|
||||
|
||||
try {
|
||||
|
||||
@@ -102,22 +156,25 @@ try {
|
||||
Format::setDir(__DIR__ . '/formats/');
|
||||
Cache::setDir(__DIR__ . '/caches/');
|
||||
|
||||
if(!file_exists($whitelist_file)) {
|
||||
if(!file_exists(WHITELIST_FILE)) {
|
||||
$whitelist_selection = $whitelist_default;
|
||||
$whitelist_write = implode("\n", $whitelist_default);
|
||||
file_put_contents($whitelist_file, $whitelist_write);
|
||||
file_put_contents(WHITELIST_FILE, $whitelist_write);
|
||||
} else {
|
||||
|
||||
$whitelist_file_content = file_get_contents($whitelist_file);
|
||||
$whitelist_file_content = file_get_contents(WHITELIST_FILE);
|
||||
if($whitelist_file_content != "*\n") {
|
||||
$whitelist_selection = explode("\n", $whitelist_file_content);
|
||||
} else {
|
||||
$whitelist_selection = Bridge::listBridges();
|
||||
}
|
||||
|
||||
// Prepare for case-insensitive match
|
||||
$whitelist_selection = array_map('strtolower', $whitelist_selection);
|
||||
}
|
||||
|
||||
$action = filter_input(INPUT_GET, 'action');
|
||||
$bridge = filter_input(INPUT_GET, 'bridge');
|
||||
$action = array_key_exists('action', $params) ? $params['action'] : null;
|
||||
$bridge = array_key_exists('bridge', $params) ? $params['bridge'] : null;
|
||||
|
||||
if($action === 'display' && !empty($bridge)) {
|
||||
// DEPRECATED: 'nameBridge' scheme is replaced by 'name' in bridge parameter values
|
||||
@@ -126,7 +183,8 @@ try {
|
||||
$bridge = substr($bridge, 0, $pos);
|
||||
}
|
||||
|
||||
$format = filter_input(INPUT_GET, 'format');
|
||||
$format = $params['format']
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
// DEPRECATED: 'nameFormat' scheme is replaced by 'name' in format parameter values
|
||||
// this is to keep compatibility until futher complete removal
|
||||
@@ -135,7 +193,7 @@ try {
|
||||
}
|
||||
|
||||
// whitelist control
|
||||
if(!Bridge::isWhitelisted($whitelist_selection, $bridge)) {
|
||||
if(!Bridge::isWhitelisted($whitelist_selection, strtolower($bridge))) {
|
||||
throw new \HttpException('This bridge is not whitelisted', 401);
|
||||
die;
|
||||
}
|
||||
@@ -143,12 +201,20 @@ try {
|
||||
// Data retrieval
|
||||
$bridge = Bridge::create($bridge);
|
||||
|
||||
$noproxy = filter_input(INPUT_GET, '_noproxy', FILTER_VALIDATE_BOOLEAN);
|
||||
$noproxy = array_key_exists('_noproxy', $params) && filter_var($params['_noproxy'], FILTER_VALIDATE_BOOLEAN);
|
||||
if(defined('PROXY_URL') && PROXY_BYBRIDGE && $noproxy) {
|
||||
define('NOPROXY', true);
|
||||
}
|
||||
|
||||
$params = $_GET;
|
||||
// Custom 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);
|
||||
}
|
||||
|
||||
// Initialize cache
|
||||
$cache = Cache::create('FileCache');
|
||||
@@ -160,13 +226,19 @@ try {
|
||||
unset($params['bridge']);
|
||||
unset($params['format']);
|
||||
unset($params['_noproxy']);
|
||||
unset($params['_cache_timeout']);
|
||||
|
||||
// Load cache & data
|
||||
try {
|
||||
$bridge->setCache($cache);
|
||||
$bridge->setCacheTimeout($cache_timeout);
|
||||
$bridge->setDatas($params);
|
||||
} catch(Error $e) {
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/html');
|
||||
die(buildBridgeException($e, $bridge));
|
||||
} catch(Exception $e) {
|
||||
header('HTTP/1.1 ' . $e->getCode() . ' ' . Http::getMessageForCode($e->getCode()));
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/html');
|
||||
die(buildBridgeException($e, $bridge));
|
||||
}
|
||||
@@ -177,16 +249,20 @@ try {
|
||||
$format->setItems($bridge->getItems());
|
||||
$format->setExtraInfos($bridge->getExtraInfos());
|
||||
$format->display();
|
||||
} catch(Exception $e) {
|
||||
header('HTTP/1.1 ' . $e->getCode() . ' ' . Http::getMessageForCode($e->getCode()));
|
||||
} catch(Error $e) {
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/html');
|
||||
die(buildTransformException($e, $bridge));
|
||||
} catch(Exception $e) {
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/html');
|
||||
die(buildBridgeException($e, $bridge));
|
||||
}
|
||||
|
||||
die;
|
||||
}
|
||||
} catch(HttpException $e) {
|
||||
header('HTTP/1.1 ' . $e->getCode() . ' ' . Http::getMessageForCode($e->getCode()));
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/plain');
|
||||
die($e->getMessage());
|
||||
} catch(\Exception $e) {
|
||||
@@ -205,6 +281,7 @@ $formats = Format::searchInformation();
|
||||
<title>RSS-Bridge</title>
|
||||
<link href="static/style.css" rel="stylesheet">
|
||||
<script src="static/search.js"></script>
|
||||
<script src="static/select.js"></script>
|
||||
<noscript>
|
||||
<style>
|
||||
.searchbar {
|
||||
@@ -221,6 +298,8 @@ $formats = Format::searchInformation();
|
||||
$status .= 'debug mode active';
|
||||
}
|
||||
|
||||
$query = filter_input(INPUT_GET, 'q');
|
||||
|
||||
echo <<<EOD
|
||||
<header>
|
||||
<h1>RSS-Bridge</h1>
|
||||
@@ -231,7 +310,7 @@ $formats = Format::searchInformation();
|
||||
<h3>Search</h3>
|
||||
<input type="text" name="searchfield"
|
||||
id="searchfield" placeholder="Enter the bridge you want to search for"
|
||||
onchange="search()" onkeyup="search()">
|
||||
onchange="search()" onkeyup="search()" value="{$query}">
|
||||
</section>
|
||||
|
||||
EOD;
|
||||
@@ -241,7 +320,7 @@ EOD;
|
||||
$inactiveBridges = '';
|
||||
$bridgeList = Bridge::listBridges();
|
||||
foreach($bridgeList as $bridgeName) {
|
||||
if(Bridge::isWhitelisted($whitelist_selection, $bridgeName)) {
|
||||
if(Bridge::isWhitelisted($whitelist_selection, strtolower($bridgeName))) {
|
||||
echo displayBridgeCard($bridgeName, $formats);
|
||||
$activeFoundBridgeCount++;
|
||||
} elseif($showInactive) {
|
||||
@@ -252,7 +331,7 @@ EOD;
|
||||
echo $inactiveBridges;
|
||||
?>
|
||||
<section class="footer">
|
||||
<a href="https://github.com/RSS-Bridge/rss-bridge">RSS-Bridge 2017-08-03 ~ Public Domain</a><br />
|
||||
<a href="https://github.com/RSS-Bridge/rss-bridge">RSS-Bridge 2018-06-10 ~ Public Domain</a><br />
|
||||
<?= $activeFoundBridgeCount; ?>/<?= count($bridgeList) ?> active bridges. <br />
|
||||
<?php
|
||||
if($activeFoundBridgeCount !== count($bridgeList)) {
|
||||
|
@@ -8,16 +8,6 @@ class Bridge {
|
||||
throw new \LogicException('Please use ' . __CLASS__ . '::create for new object.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a bridge is an instantiable bridge.
|
||||
* @param string $nameBridge name of the bridge that you want to use
|
||||
* @return true if it is an instantiable bridge, false otherwise.
|
||||
*/
|
||||
static public function isInstantiable($nameBridge){
|
||||
$re = new ReflectionClass($nameBridge);
|
||||
return $re->IsInstantiable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new bridge object
|
||||
* @param string $nameBridge Defined bridge name you want use
|
||||
@@ -42,11 +32,11 @@ EOD;
|
||||
|
||||
require_once $pathBridge;
|
||||
|
||||
if(Bridge::isInstantiable($nameBridge)) {
|
||||
if((new ReflectionClass($nameBridge))->isInstantiable()) {
|
||||
return new $nameBridge();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static public function setDir($dirBridge){
|
||||
@@ -62,13 +52,11 @@ EOD;
|
||||
}
|
||||
|
||||
static public function getDir(){
|
||||
$dirBridge = self::$dirBridge;
|
||||
|
||||
if(is_null($dirBridge)) {
|
||||
if(is_null(self::$dirBridge)) {
|
||||
throw new \LogicException(__CLASS__ . ' class need to know bridge path !');
|
||||
}
|
||||
|
||||
return $dirBridge;
|
||||
return self::$dirBridge;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,9 +64,8 @@ EOD;
|
||||
* @return array List of the bridges
|
||||
*/
|
||||
static public function listBridges(){
|
||||
$pathDirBridge = self::getDir();
|
||||
$listBridge = array();
|
||||
$dirFiles = scandir($pathDirBridge);
|
||||
$dirFiles = scandir(self::getDir());
|
||||
|
||||
if($dirFiles !== false) {
|
||||
foreach($dirFiles as $fileName) {
|
||||
@@ -92,14 +79,10 @@ EOD;
|
||||
}
|
||||
|
||||
static public function isWhitelisted($whitelist, $name){
|
||||
if(in_array($name, $whitelist)
|
||||
return in_array($name, $whitelist)
|
||||
|| in_array($name . '.php', $whitelist)
|
||||
|| in_array($name . 'Bridge', $whitelist) // DEPRECATED
|
||||
|| in_array($name . 'Bridge.php', $whitelist) // DEPRECATED
|
||||
|| (count($whitelist) === 1 && trim($whitelist[0]) === '*')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|| in_array($name . 'bridge', $whitelist) // DEPRECATED
|
||||
|| in_array($name . 'bridge.php', $whitelist) // DEPRECATED
|
||||
|| (count($whitelist) === 1 && trim($whitelist[0]) === '*');
|
||||
}
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
protected $items = array();
|
||||
protected $inputs = array();
|
||||
protected $queriedContext = '';
|
||||
protected $cacheTimeout;
|
||||
|
||||
/**
|
||||
* Return cachable datas (extrainfos and items) stored in the bridge
|
||||
@@ -171,7 +172,7 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
if(!is_null($this->cache)) {
|
||||
$time = $this->cache->getTime();
|
||||
if($time !== false
|
||||
&& (time() - static::CACHE_TIMEOUT < $time)
|
||||
&& (time() - $this->getCacheTimeout() < $time)
|
||||
&& (!defined('DEBUG') || DEBUG !== true)) {
|
||||
$cached = $this->cache->loadData();
|
||||
if(isset($cached['items']) && isset($cached['extraInfos'])) {
|
||||
@@ -268,4 +269,17 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -68,4 +68,20 @@ interface BridgeInterface {
|
||||
* @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
|
||||
*
|
||||
* @return int Cache timeout
|
||||
*/
|
||||
public function getCacheTimeout();
|
||||
}
|
||||
|
@@ -1,64 +1,6 @@
|
||||
<?php
|
||||
class HttpException extends \Exception{}
|
||||
|
||||
/**
|
||||
* Not real http implementation but only utils stuff
|
||||
*/
|
||||
class Http{
|
||||
|
||||
/**
|
||||
* Return message corresponding to Http code
|
||||
*/
|
||||
static public function getMessageForCode($code){
|
||||
$codes = self::getCodes();
|
||||
|
||||
if(isset($codes[$code]))
|
||||
return $codes[$code];
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* List of common Http code
|
||||
*/
|
||||
static public function getCodes(){
|
||||
return array(
|
||||
200 => 'OK',
|
||||
201 => 'Created',
|
||||
202 => 'Accepted',
|
||||
300 => 'Multiple Choices',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Moved Temporarily',
|
||||
307 => 'Temporary Redirect',
|
||||
310 => 'Too many Redirects',
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
402 => 'Payment Required',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not',
|
||||
406 => 'Not Acceptable',
|
||||
407 => 'Proxy Authentication Required',
|
||||
408 => 'Request Time-out',
|
||||
409 => 'Conflict',
|
||||
410 => 'Gone',
|
||||
411 => 'Length Required',
|
||||
412 => 'Precondition Failed',
|
||||
413 => 'Request Entity Too Large',
|
||||
414 => 'Request-URI Too Long',
|
||||
415 => 'Unsupported Media Type',
|
||||
416 => 'Requested range unsatisfiable',
|
||||
417 => 'Expectation failed',
|
||||
500 => 'Internal Server Error',
|
||||
501 => 'Not Implemented',
|
||||
502 => 'Bad Gateway',
|
||||
503 => 'Service Unavailable',
|
||||
504 => 'Gateway Time-out',
|
||||
508 => 'Loop detected',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an URL that automatically populates a new issue on GitHub based
|
||||
* on the information provided
|
||||
@@ -112,7 +54,7 @@ function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null
|
||||
* provided parameter are invalid
|
||||
*/
|
||||
function buildBridgeException($e, $bridge){
|
||||
if(!($e instanceof \Exception) || !($bridge instanceof \BridgeInterface)) {
|
||||
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -143,7 +85,7 @@ unable to receive or process the remote website's content!";
|
||||
* provided parameter are invalid
|
||||
*/
|
||||
function buildTransformException($e, $bridge){
|
||||
if(!($e instanceof \Exception) || !($bridge instanceof \BridgeInterface)) {
|
||||
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@@ -18,7 +18,7 @@ abstract class FeedExpander extends BridgeAbstract {
|
||||
*/
|
||||
$content = getContents($url)
|
||||
or returnServerError('Could not request ' . $url);
|
||||
$rssContent = simplexml_load_string($content);
|
||||
$rssContent = simplexml_load_string(trim($content));
|
||||
|
||||
debugMessage('Detecting feed format/version');
|
||||
switch(true) {
|
||||
@@ -102,12 +102,12 @@ abstract class FeedExpander extends BridgeAbstract {
|
||||
if(!isset($content->link)) {
|
||||
$this->uri = '';
|
||||
} elseif (count($content->link) === 1) {
|
||||
$this->uri = $content->link[0]['href'];
|
||||
$this->uri = (string)$content->link[0]['href'];
|
||||
} else {
|
||||
$this->uri = '';
|
||||
foreach($content->link as $link) {
|
||||
if(strtolower($link['rel']) === 'alternate') {
|
||||
$this->uri = $link['href'];
|
||||
$this->uri = (string)$link['href'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -1,77 +1,47 @@
|
||||
<?php
|
||||
function getContents($url,
|
||||
$use_include_path = false,
|
||||
$context = null,
|
||||
$offset = 0,
|
||||
$maxlen = null){
|
||||
$contextOptions = array(
|
||||
'http' => array(
|
||||
'user_agent' => ini_get('user_agent'),
|
||||
'accept_encoding' => 'gzip'
|
||||
)
|
||||
);
|
||||
function getContents($url, $header = array(), $opts = array()){
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
if(is_array($header) && count($header) !== 0)
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
|
||||
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, ini_get('user_agent'));
|
||||
curl_setopt($ch, CURLOPT_ENCODING, '');
|
||||
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
||||
|
||||
if(is_array($opts)) {
|
||||
foreach($opts as $key => $value) {
|
||||
curl_setopt($ch, $key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
if(defined('PROXY_URL') && !defined('NOPROXY')) {
|
||||
$contextOptions['http']['proxy'] = PROXY_URL;
|
||||
$contextOptions['http']['request_fulluri'] = true;
|
||||
|
||||
if(is_null($context)) {
|
||||
$context = stream_context_create($contextOptions);
|
||||
} else {
|
||||
$prevContext = $context;
|
||||
if(!stream_context_set_option($context, $contextOptions)) {
|
||||
$context = $prevContext;
|
||||
}
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_PROXY, PROXY_URL);
|
||||
}
|
||||
|
||||
if(is_null($maxlen)) {
|
||||
$content = file_get_contents($url, $use_include_path, $context, $offset);
|
||||
} else {
|
||||
$content = file_get_contents($url, $use_include_path, $context, $offset, $maxlen);
|
||||
}
|
||||
$content = curl_exec($ch);
|
||||
$curlError = curl_error($ch);
|
||||
$curlErrno = curl_errno($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if($content === false)
|
||||
debugMessage('Cant\'t download ' . $url);
|
||||
|
||||
// handle compressed data
|
||||
foreach($http_response_header as $header) {
|
||||
if(stristr($header, 'content-encoding')) {
|
||||
switch(true) {
|
||||
case stristr($header, 'gzip'):
|
||||
$content = gzinflate(substr($content, 10, -8));
|
||||
break;
|
||||
case stristr($header, 'compress'):
|
||||
//TODO
|
||||
case stristr($header, 'deflate'):
|
||||
//TODO
|
||||
case stristr($header, 'brotli'):
|
||||
//TODO
|
||||
returnServerError($header . '=> Not implemented yet');
|
||||
break;
|
||||
case stristr($header, 'identity'):
|
||||
break;
|
||||
default:
|
||||
returnServerError($header . '=> Unknown compression');
|
||||
}
|
||||
}
|
||||
}
|
||||
debugMessage('Cant\'t download ' . $url . ' cUrl error: ' . $curlError . ' (' . $curlErrno . ')');
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
function getSimpleHTMLDOM($url,
|
||||
$use_include_path = false,
|
||||
$context = null,
|
||||
$offset = 0,
|
||||
$maxLen = null,
|
||||
$header = array(),
|
||||
$opts = array(),
|
||||
$lowercase = true,
|
||||
$forceTagsClosed = true,
|
||||
$target_charset = DEFAULT_TARGET_CHARSET,
|
||||
$stripRN = true,
|
||||
$defaultBRText = DEFAULT_BR_TEXT,
|
||||
$defaultSpanText = DEFAULT_SPAN_TEXT){
|
||||
$content = getContents($url, $use_include_path, $context, $offset, $maxLen);
|
||||
$content = getContents($url, $header, $opts);
|
||||
return str_get_html($content,
|
||||
$lowercase,
|
||||
$forceTagsClosed,
|
||||
@@ -89,10 +59,8 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
|
||||
*/
|
||||
function getSimpleHTMLDOMCached($url,
|
||||
$duration = 86400,
|
||||
$use_include_path = false,
|
||||
$context = null,
|
||||
$offset = 0,
|
||||
$maxLen = null,
|
||||
$header = array(),
|
||||
$opts = array(),
|
||||
$lowercase = true,
|
||||
$forceTagsClosed = true,
|
||||
$target_charset = DEFAULT_TARGET_CHARSET,
|
||||
@@ -116,7 +84,7 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
|
||||
&& (!defined('DEBUG') || DEBUG !== true)) { // Contents within duration
|
||||
$content = $cache->loadData();
|
||||
} else { // Content not within duration
|
||||
$content = getContents($url, $use_include_path, $context, $offset, $maxLen);
|
||||
$content = getContents($url, $header, $opts);
|
||||
if($content !== false) {
|
||||
$cache->saveData($content);
|
||||
}
|
||||
|
35
lib/html.php
35
lib/html.php
@@ -75,8 +75,24 @@ CARD;
|
||||
. ((defined('PROXY_NAME') && PROXY_NAME) ? PROXY_NAME : PROXY_URL)
|
||||
. ')</label><br />'
|
||||
. PHP_EOL;
|
||||
}
|
||||
} if(CUSTOM_CACHE_TIMEOUT) {
|
||||
$idArg = 'arg-'
|
||||
. urlencode($bridgeName)
|
||||
. '-'
|
||||
. urlencode('_cache_timeout');
|
||||
|
||||
$card .= '<label for="'
|
||||
. $idArg
|
||||
. '">Cache timeout in seconds : </label>'
|
||||
. PHP_EOL;
|
||||
|
||||
$card .= '<input id="'
|
||||
. $idArg
|
||||
. '" type="number" value="'
|
||||
. $bridge->getCacheTimeout()
|
||||
. '" name="_cache_timeout" /><br />'
|
||||
. PHP_EOL;
|
||||
}
|
||||
$card .= $getHelperButtonsFormat($formats);
|
||||
} else {
|
||||
$card .= '<span style="font-weight: bold;">Inactive</span>';
|
||||
@@ -251,6 +267,23 @@ CARD;
|
||||
. ((defined('PROXY_NAME') && PROXY_NAME) ? PROXY_NAME : PROXY_URL)
|
||||
. ')</label><br />'
|
||||
. PHP_EOL;
|
||||
} if(CUSTOM_CACHE_TIMEOUT) {
|
||||
$idArg = 'arg-'
|
||||
. urlencode($bridgeName)
|
||||
. '-'
|
||||
. urlencode('_cache_timeout');
|
||||
|
||||
$card .= '<label for="'
|
||||
. $idArg
|
||||
. '">Cache timeout in seconds : </label>'
|
||||
. PHP_EOL;
|
||||
|
||||
$card .= '<input id="'
|
||||
. $idArg
|
||||
. '" type="number" value="'
|
||||
. $bridge->getCacheTimeout()
|
||||
. '" name="_cache_timeout" /><br />'
|
||||
. PHP_EOL;
|
||||
}
|
||||
$card .= $getHelperButtonsFormat($formats);
|
||||
} else {
|
||||
|
@@ -21,19 +21,14 @@ function validateData(&$data, $parameters){
|
||||
$validateNumberValue = function($value){
|
||||
$filteredValue = filter_var($value, FILTER_VALIDATE_INT);
|
||||
|
||||
if($filteredValue === false && !empty($value))
|
||||
if($filteredValue === false)
|
||||
return null;
|
||||
|
||||
return $filteredValue;
|
||||
};
|
||||
|
||||
$validateCheckboxValue = function($value){
|
||||
$filteredValue = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
|
||||
if(is_null($filteredValue))
|
||||
return null;
|
||||
|
||||
return $filteredValue;
|
||||
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
};
|
||||
|
||||
$validateListValue = function($value, $expectedValues){
|
||||
@@ -85,7 +80,7 @@ function validateData(&$data, $parameters){
|
||||
break;
|
||||
}
|
||||
|
||||
if(is_null($data[$name])) {
|
||||
if(is_null($data[$name]) && isset($set[$name]['required']) && $set[$name]['required']) {
|
||||
echo 'Parameter \'' . $name . '\' is invalid!' . PHP_EOL;
|
||||
return false;
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockq
|
||||
border: 0;
|
||||
outline: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
|
||||
}
|
||||
@@ -111,3 +110,8 @@ button.backbutton, button.rss-feed {
|
||||
|
||||
}
|
||||
|
||||
img {
|
||||
|
||||
max-width: 100%;
|
||||
|
||||
}
|
10
static/select.js
Normal file
10
static/select.js
Normal file
@@ -0,0 +1,10 @@
|
||||
function select(){
|
||||
var fragment = window.location.hash.substr(1);
|
||||
var bridge = document.getElementById(fragment);
|
||||
|
||||
if(bridge !== null) {
|
||||
bridge.getElementsByClassName('showmore-box')[0].checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', select);
|
@@ -52,6 +52,18 @@ header > p.status {
|
||||
color: red;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
|
||||
background-color: white;
|
||||
color: #404552;
|
||||
border: 0px;
|
||||
border-bottom: 2px solid #2196F3;
|
||||
font-size: 1.1em;
|
||||
margin-left: 8px;
|
||||
padding-left: 4px;
|
||||
|
||||
}
|
||||
|
||||
.searchbar {
|
||||
|
||||
width: 50%;
|
||||
@@ -64,6 +76,7 @@ header > p.status {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
font-size: 1.4em;
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
@@ -73,6 +86,30 @@ header > p.status {
|
||||
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
||||
font-size: 150%;
|
||||
@@ -188,18 +225,6 @@ form {
|
||||
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
|
||||
background-color: white;
|
||||
color: #404552;
|
||||
border: 0px;
|
||||
border-bottom: 2px solid #2196F3;
|
||||
font-size: 1.1em;
|
||||
margin-left: 8px;
|
||||
padding-left: 4px;
|
||||
|
||||
}
|
||||
|
||||
form {
|
||||
|
||||
display: none;
|
||||
|
Reference in New Issue
Block a user