mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-17 22:02:09 +02:00
Compare commits
63 Commits
2017-08-19
...
2018-03-11
Author | SHA1 | Date | |
---|---|---|---|
|
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 |
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
|
||||
|
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,39 @@ 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] . '}]}';
|
||||
}
|
||||
$context = array(
|
||||
'http' => array(
|
||||
'header' => 'Authorization: Bearer '. self::API_TOKEN
|
||||
)
|
||||
);
|
||||
|
||||
$input_json = json_decode(html_entity_decode($input, ENT_QUOTES), true);
|
||||
$input = getContents($url, false, stream_context_create($context)) 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;
|
||||
}
|
||||
}
|
||||
}
|
474
bridges/DealabsBridge.php
Normal file
474
bridges/DealabsBridge.php
Normal file
@@ -0,0 +1,474 @@
|
||||
<?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',
|
||||
'box--all-i',
|
||||
'thread-listImgCell',
|
||||
)
|
||||
);
|
||||
|
||||
// Deal Link CSS Selector
|
||||
$selectorLink = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'cept-tt',
|
||||
'thread-link',
|
||||
'linkPlain',
|
||||
'space--r-1',
|
||||
'size--all-s',
|
||||
'size--fromW3-m',
|
||||
)
|
||||
);
|
||||
|
||||
// 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',
|
||||
'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) {
|
||||
return '<div>Réduction : <span style="text-decoration: line-through;">'
|
||||
. $deal->find(
|
||||
'span[class*=mute--text text--lineThrough]', 0
|
||||
)->plaintext
|
||||
. '</span> '
|
||||
. $deal->find('span[class=space--ml-1 size--all-l size--fromW3-xl]', 0)->plaintext
|
||||
. '</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'
|
||||
);
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
146
bridges/DemonoidBridge.php
Normal file
146
bridges/DemonoidBridge.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
class DemonoidBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'metaMMA';
|
||||
const NAME = 'Demonoid';
|
||||
const URI = 'https://www.demonoid.pw/';
|
||||
const DESCRIPTION = 'Returns results for the keywords (in all categories or
|
||||
a specific category). You can put several keywords separated by a semicolon
|
||||
(e.g. "one show;another show"). Searches can by done in a specific category;
|
||||
category number must be specified. (All=0, Movies=1, Music=2, TV=3, Games=4,
|
||||
Applications=5, Pictures=8, Anime=9, Comics=10, Books=11 Music Videos=8,
|
||||
Audio Books=17). User feed takes the Uploader ID number (not uploader name)
|
||||
as keyword. Uploader ID is found by clicking on uploader, clicking on
|
||||
"View this user\'s torrents", and copying the number after "uid=". An entire
|
||||
category feed is accomplished by leaving "keywords" box blank and using the
|
||||
corresponding category number.';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'q' => array(
|
||||
'name' => 'keywords/user ID/category, separated by semicolons',
|
||||
'exampleValue' => 'first list;second list;…',
|
||||
'required' => true
|
||||
),
|
||||
'crit' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Feed type',
|
||||
'values' => array(
|
||||
'search' => 'search',
|
||||
'category' => 'cat',
|
||||
'user' => 'usr'
|
||||
)
|
||||
),
|
||||
'catCheck' => array(
|
||||
'type' => 'checkbox',
|
||||
'name' => 'Specify category for keyword search ?',
|
||||
),
|
||||
'cat' => array(
|
||||
'name' => 'Category number',
|
||||
),
|
||||
));
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$catBool = $this->getInput('catCheck');
|
||||
if($catBool) {
|
||||
$catNum = $this->getInput('cat');
|
||||
}
|
||||
$critList = $this->getInput('crit');
|
||||
|
||||
$keywordsList = explode(';', $this->getInput('q'));
|
||||
foreach($keywordsList as $keywords) {
|
||||
switch($critList) {
|
||||
case 'search':
|
||||
if($catBool == false) {
|
||||
$html = file_get_contents(
|
||||
self::URI .
|
||||
'files/?category=0&subcategory=All&quality=All&seeded=2&external=2&query=' .
|
||||
urlencode($keywords) . #not rawurlencode so space -> '+'
|
||||
'&uid=0&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
} else {
|
||||
$html = file_get_contents(
|
||||
self::URI .
|
||||
'files/?category=' .
|
||||
rawurlencode($catNum) .
|
||||
'&subcategory=All&quality=All&seeded=2&external=2&query=' .
|
||||
urlencode($keywords) . #not rawurlencode so space -> '+'
|
||||
'&uid=0&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
}
|
||||
break;
|
||||
case 'usr':
|
||||
$html = file_get_contents(
|
||||
self::URI .
|
||||
'files/?uid=' .
|
||||
rawurlencode($keywords) .
|
||||
'&seeded=2'
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
break;
|
||||
case 'cat':
|
||||
$html = file_get_contents(
|
||||
self::URI .
|
||||
'files/?uid=0&category=' .
|
||||
rawurlencode($keywords) .
|
||||
'&subcategory=0&language=0&seeded=2&quality=0&query=&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
break;
|
||||
}
|
||||
|
||||
if(preg_match('~No torrents found~', $html)) {
|
||||
returnServerError('No result for query ' . $keywords);
|
||||
}
|
||||
|
||||
$bigTable = explode('<!-- start torrent list -->', $html)[1];
|
||||
$last50 = explode('<!-- end torrent list -->', $bigTable)[0];
|
||||
$dateChunk = explode('added_today', $last50);
|
||||
$item = array ();
|
||||
|
||||
for($block = 1;$block < count($dateChunk);$block++) {
|
||||
preg_match('~(?<=>Add).*?(?=<)~', $dateChunk[$block], $dateStr);
|
||||
if(preg_match('~today~', $dateStr[0])) {
|
||||
date_default_timezone_set('UTC');
|
||||
$timestamp = mktime(0, 0, 0, gmdate('n'), gmdate('j'), gmdate('Y'));
|
||||
} else {
|
||||
preg_match('~(?<=ed on ).*\d+~', $dateStr[0], $fullDateStr);
|
||||
date_default_timezone_set('UTC');
|
||||
$dateObj = strptime($fullDateStr[0], '%A, %b %d, %Y');
|
||||
$timestamp = mktime(0, 0, 0, $dateObj['tm_mon'] + 1, $dateObj['tm_mday'], 1900 + $dateObj['tm_year']);
|
||||
}
|
||||
|
||||
$itemsChunk = explode('<!-- tstart -->', $dateChunk[$block]);
|
||||
|
||||
for($items = 1;$items < count($itemsChunk);$items++) {
|
||||
$item = array();
|
||||
$cols = explode('<td', $itemsChunk[$items]);
|
||||
preg_match('~(?<=href=\"/).*?(?=\")~', $cols[1], $matches);
|
||||
$item['id'] = self::URI . $matches[0];
|
||||
preg_match('~(?<=href=\").*?(?=\")~', $cols[4], $matches);
|
||||
$item['uri'] = $matches[0];
|
||||
$item['timestamp'] = $timestamp;
|
||||
preg_match('~(?<=href=\"/users/).*?(?=\")~', $cols[3], $matches);
|
||||
$item['author'] = $matches[0];
|
||||
preg_match('~(?<=/\">).*?(?=</a>)~', $cols[1], $matches);
|
||||
$item['title'] = $matches[0];
|
||||
preg_match('~(?<=green\">)\d+(?=</font>)~', $cols[8], $matches);
|
||||
$item['seeders'] = $matches[0];
|
||||
preg_match('~(?<=red\">)\d+(?=</font>)~', $cols[9], $matches);
|
||||
$item['leechers'] = $matches[0];
|
||||
preg_match('~(?<=>).*?(?=</td>)~', $cols[5], $matches);
|
||||
$item['size'] = $matches[0];
|
||||
$item['content'] = 'Uploaded by ' . $item['author']
|
||||
. ' , Size ' . $item['size']
|
||||
. '<br>seeders: '
|
||||
. $item['seeders']
|
||||
. ' | leechers: '
|
||||
. $item['leechers']
|
||||
. '<br><a href="'
|
||||
. $item['id']
|
||||
. '">info page</a>';
|
||||
if(isset($item['title']))
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 . '"';
|
||||
|
@@ -20,7 +20,7 @@ class GoComicsBridge extends BridgeAbstract {
|
||||
|
||||
foreach($html->find('div.comic__container') as $element) {
|
||||
|
||||
$img = $element->find('img', 0);
|
||||
$img = $element->find('.item-comic-image img', 0);
|
||||
$link = $element->find('a.js-item-comic-link', 0);
|
||||
$comic = $img->src;
|
||||
$title = $link->title;
|
||||
|
307
bridges/IPBBridge.php
Normal file
307
bridges/IPBBridge.php
Normal file
@@ -0,0 +1,307 @@
|
||||
<?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' => 'Specify how many pages should be fetched (-1: all)',
|
||||
'defaultValue' => 1
|
||||
)
|
||||
)
|
||||
);
|
||||
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
|
||||
|
||||
do {
|
||||
// Skip loading HTML on first iteration
|
||||
if(!is_null($next)) {
|
||||
$html = getSimpleHTMLDOMCached($next);
|
||||
}
|
||||
|
||||
$next = $this->$callback($html, is_null($next));
|
||||
$limit--;
|
||||
} while(!is_null($next) && $limit <> 0);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@@ -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) {
|
||||
|
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){
|
||||
|
@@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
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 MAINTAINER = 'jacknumber';
|
||||
@@ -68,62 +68,65 @@ class SteamBridge extends BridgeAbstract {
|
||||
|
||||
$username = $this->getInput('username');
|
||||
$params = array(
|
||||
'sort' => $this->getInput('sort'),
|
||||
'cc' => $this->getInput('currency')
|
||||
'cc' => $this->getInput('currency'),
|
||||
'sort' => $this->getInput('sort')
|
||||
);
|
||||
|
||||
$url = self::URI . 'id/' . $username . '/wishlist?' . http_build_query($params);
|
||||
$url = self::URI . 'wishlist/id/' . $username . '/?' . http_build_query($params);
|
||||
|
||||
$html = '';
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
$jsonDataRegex = '/var g_rg(?:WishlistData|AppInfo) = ([^;]*)/';
|
||||
$content = getContents($url)
|
||||
or returnServerError("Could not request Steam Wishlist. Tried:\n - $url");
|
||||
|
||||
foreach($html->find('#wishlist_items .wishlistRow') as $element) {
|
||||
preg_match_all($jsonDataRegex, $content, $matches, PREG_SET_ORDER, 0);
|
||||
|
||||
$gameTitle = $element->find('h4', 0)->plaintext;
|
||||
$gameUri = $element->find('.storepage_btn_ctn a', 0)->href;
|
||||
$gameImg = $element->find('.gameListRowLogo img', 0)->src;
|
||||
$appList = json_decode($matches[0][1], true);
|
||||
$fullAppList = json_decode($matches[1][1], true);
|
||||
//var_dump($matches[1][1]);
|
||||
//var_dump($fullAppList);
|
||||
$sortedElementList = array_fill(0, count($appList), 0);
|
||||
foreach($appList as $app) {
|
||||
|
||||
$discountBlock = $element->find('.discount_block', 0);
|
||||
$sortedElementList[$app["priority"] - 1] = $app["appid"];
|
||||
|
||||
if($element->find('.discount_block', 0)) {
|
||||
$gameHasPromo = 1;
|
||||
} else {
|
||||
}
|
||||
|
||||
if($this->getInput('only_discount')) {
|
||||
continue;
|
||||
}
|
||||
foreach($sortedElementList as $appId) {
|
||||
|
||||
$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;
|
||||
}
|
||||
$app = $fullAppList[$appId];
|
||||
$gameTitle = $app["name"];
|
||||
$gameUri = "http://store.steampowered.com/app/" . $appId . "/";
|
||||
$gameImg = $app["capsule"];
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $gameUri;
|
||||
$item['title'] = $gameTitle;
|
||||
$item['price'] = $gamePrice;
|
||||
$item['hasPromo'] = $gameHasPromo;
|
||||
|
||||
if($gameHasPromo) {
|
||||
if(count($app["subs"]) > 0) {
|
||||
if($app["subs"][0]["discount_pct"] != 0) {
|
||||
|
||||
$item['promoValue'] = $gamePromoValue;
|
||||
$item['oldPrice'] = $gameOldPrice;
|
||||
$item['newPrice'] = $gameNewPrice;
|
||||
$item['promoValue'] = $app["subs"][0]["discount_pct"];
|
||||
$item['oldPrice'] = $app["subs"][0]["price"] / 100 / ((100 - $gamePromoValue / 100));
|
||||
$item['newPrice'] = $app["subs"][0]["price"] / 100;
|
||||
$item['price'] = $item['newPrice'];
|
||||
|
||||
$item['hasPromo'] = true;
|
||||
|
||||
} else {
|
||||
|
||||
if($this->getInput('only_discount')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['price'] = $app["subs"][0]["price"] / 100;
|
||||
$item['hasPromo'] = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
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,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,42 +17,54 @@ 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);
|
||||
$html = str_get_html($text_html);
|
||||
$pageName = $html->find('.page_name', 0)->plaintext;
|
||||
$this->pageName = $pageName;
|
||||
|
||||
foreach($html->find('.post') as $post) {
|
||||
foreach ($html->find('.post') as $post) {
|
||||
|
||||
if(is_object($post->find('a.wall_post_more', 0))) {
|
||||
if (is_object($post->find('a.wall_post_more', 0))) {
|
||||
//delete link "show full" in content
|
||||
$post->find('a.wall_post_more', 0)->outertext = '';
|
||||
}
|
||||
$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');
|
||||
|
||||
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));
|
||||
. str_replace('/away.php?to=', '', urldecode($link));
|
||||
}
|
||||
|
||||
//get video on post
|
||||
if(is_object($post->find('span.post_video_title_content', 0))) {
|
||||
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}";
|
||||
@@ -58,9 +72,57 @@ class VkBridge extends BridgeAbstract {
|
||||
|
||||
// get post link
|
||||
$item['uri'] = self::URI . $post->find('a.post_link', 0)->getAttribute('href');
|
||||
$item['date'] = $post->find('span.rel_date', 0)->plaintext;
|
||||
$item['timestamp'] = $this->getTime($post);
|
||||
$item['author'] = $pageName;
|
||||
$this->items[] = $item;
|
||||
// var_dump($item['date']);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
$opts = array(
|
||||
'http' => array(
|
||||
'method' => "GET",
|
||||
'user_agent' => ini_get('user_agent'),
|
||||
'accept_encoding' => 'gzip',
|
||||
'header' => "Accept-language: en\r\n
|
||||
Cookie: remixlang=3\r\n"
|
||||
)
|
||||
);
|
||||
|
||||
$context = stream_context_create($opts);
|
||||
|
||||
return getContents($this->getURI(), false, $context);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -50,12 +50,28 @@ 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'));
|
||||
|
||||
if(!is_null($html->find('div#watch-description-text', 0)))
|
||||
$desc = $html->find('div#watch-description-text', 0)->innertext;
|
||||
// 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'));
|
||||
@@ -88,9 +104,10 @@ 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){
|
||||
@@ -104,7 +121,7 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
$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]') {
|
||||
if($title != '[Private Video]' && strpos($vid, 'googleads') === false) {
|
||||
$this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time);
|
||||
$this->ytBridgeAddItem($vid, $title, $author, $desc, $time);
|
||||
$count++;
|
||||
@@ -163,7 +180,7 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
$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);
|
||||
$this->feedName = 'Playlist: ' . str_replace(' - YouTube', '', $html->find('title', 0)->plaintext); // feedName will be used by getName()
|
||||
} elseif($this->getInput('s')) { /* search mode */
|
||||
$this->request = $this->getInput('s');
|
||||
$page = 1;
|
||||
@@ -181,7 +198,7 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
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->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=...)");
|
||||
@@ -189,6 +206,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
22
index.php
22
index.php
@@ -28,6 +28,14 @@ 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 file,
|
||||
@@ -62,6 +70,9 @@ 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"');
|
||||
|
||||
// configuration checks
|
||||
if(ini_get('allow_url_fopen') !== "1")
|
||||
die('"allow_url_fopen" is not set to "1". Please check "php.ini');
|
||||
@@ -124,8 +135,8 @@ try {
|
||||
$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
|
||||
@@ -134,7 +145,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
|
||||
@@ -151,13 +163,11 @@ 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;
|
||||
|
||||
// Initialize cache
|
||||
$cache = Cache::create('FileCache');
|
||||
$cache->setPath(CACHE_DIR);
|
||||
|
Reference in New Issue
Block a user