mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-18 14:22:38 +02:00
Compare commits
101 Commits
2018-04-06
...
2018-08-07
Author | SHA1 | Date | |
---|---|---|---|
|
de7622ebbf | ||
|
09c9d015b4 | ||
|
3a496e3b18 | ||
|
df58f5bbdb | ||
|
9d0452d11b | ||
|
f92ac49947 | ||
|
a574fa15ac | ||
|
8f9a385b4d | ||
|
53bdfa3bf0 | ||
|
53278b2eed | ||
|
5f3c55b808 | ||
|
fb79a67370 | ||
|
3c4e12ceba | ||
|
0d1923c52f | ||
|
ce896b4247 | ||
|
a4b2d88dbe | ||
|
65ec04ea98 | ||
|
afb4de318b | ||
|
43bb17f995 | ||
|
bae7a5879f | ||
|
bd760cbcee | ||
|
cd20b4476f | ||
|
d83f2f285b | ||
|
15e6d77569 | ||
|
f97d2ef254 | ||
|
91ae2a23d7 | ||
|
066ef1d7db | ||
|
4facbf32e3 | ||
|
6bd76af326 | ||
|
caa622ffec | ||
|
c4d489f018 | ||
|
6a98293fb3 | ||
|
d79630e3b8 | ||
|
1f2fe25471 | ||
|
87fc9e9156 | ||
|
c7b0c9fd31 | ||
|
fbf874cb29 | ||
|
049ee52fb5 | ||
|
3f41d0593a | ||
|
7126f5e838 | ||
|
ead7b2e8de | ||
|
0d80a19e84 | ||
|
42c699f474 | ||
|
2bc8daa101 | ||
|
bca79d3f88 | ||
|
90dc968fd1 | ||
|
da6b98851c | ||
|
71c29d4192 | ||
|
193ca87afa | ||
|
5ea79ac1fc | ||
|
937ea49271 | ||
|
95686b803c | ||
|
5087f5f79e | ||
|
4a5f190e0e | ||
|
01a2746715 | ||
|
f4a60c1777 | ||
|
1b08bce779 | ||
|
9fa74a36c6 | ||
|
7493e2b5b8 | ||
|
8e468a9ca7 | ||
|
50924b9213 | ||
|
4c5013bc82 | ||
|
7dc09db9ca | ||
|
d92da8f0f7 | ||
|
064ba456e8 | ||
|
8ac8e08abf | ||
|
c4f32c31a8 | ||
|
4369e077c2 | ||
|
1045850043 | ||
|
2d8f4dc3c5 | ||
|
779b638fb4 | ||
|
3ca59392c2 | ||
|
9b34b68180 | ||
|
79ebdc4b39 | ||
|
8770c87389 | ||
|
c1e3352218 | ||
|
00570ce1b4 | ||
|
df33dcff4e | ||
|
e60b5ab193 | ||
|
b0c7a62f74 | ||
|
57b15a089e | ||
|
4b7fbe4188 | ||
|
2390fb58b3 | ||
|
1e8d29f6ec | ||
|
644d13686c | ||
|
aa0ff1c9b1 | ||
|
539d9f1f06 | ||
|
5ece801ce7 | ||
|
4dcea6d9c9 | ||
|
d69e2521f1 | ||
|
7927d73719 | ||
|
0620f30ae0 | ||
|
795494cfce | ||
|
ba8542156c | ||
|
55f112e034 | ||
|
208fff801d | ||
|
3c9860de43 | ||
|
a16ec196c5 | ||
|
887fc7b037 | ||
|
1bd4a40f71 | ||
|
494169f959 |
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.git
|
||||||
|
cache/*
|
||||||
|
DEBUG
|
||||||
|
Dockerfile
|
||||||
|
whitelist.txt
|
||||||
|
phpcs.xml
|
||||||
|
CHANGELOG.md
|
||||||
|
CONTRIBUTING.md
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -227,8 +227,12 @@ pip-log.txt
|
|||||||
/cache
|
/cache
|
||||||
/whitelist.txt
|
/whitelist.txt
|
||||||
DEBUG
|
DEBUG
|
||||||
|
config.ini.php
|
||||||
|
|
||||||
######################
|
######################
|
||||||
## VisualStudioCode ##
|
## VisualStudioCode ##
|
||||||
######################
|
######################
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|
||||||
|
#Builder
|
||||||
|
.buildconfig
|
||||||
|
14
.travis.yml
14
.travis.yml
@@ -3,12 +3,20 @@ sudo: false
|
|||||||
language: php
|
language: php
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pear channel-update pear.php.net
|
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
|
||||||
- pear install PHP_CodeSniffer
|
composer global require squizlabs/PHP_CodeSniffer;
|
||||||
|
else
|
||||||
|
pear channel-update pear.php.net;
|
||||||
|
pear install PHP_CodeSniffer;
|
||||||
|
fi
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- phpenv rehash
|
- phpenv rehash
|
||||||
- phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p
|
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
|
||||||
|
/home/travis/.composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
||||||
|
else
|
||||||
|
phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
||||||
|
fi
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
5
Dockerfile
Normal file
5
Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
FROM ulsmith/alpine-apache-php7
|
||||||
|
|
||||||
|
COPY ./ /app/public/
|
||||||
|
|
||||||
|
RUN chown -R apache:root /app/public
|
@@ -1,6 +1,6 @@
|
|||||||
rss-bridge
|
rss-bridge
|
||||||
===
|
===
|
||||||
[](UNLICENSE) [](https://travis-ci.org/RSS-Bridge/rss-bridge)
|
[](UNLICENSE) [](https://github.com/rss-bridge/rss-bridge/releases/latest) [](https://travis-ci.org/RSS-Bridge/rss-bridge) [](https://hub.docker.com/r/rssbridge/rss-bridge/)
|
||||||
|
|
||||||
rss-bridge is a PHP project capable of generating ATOM feeds for websites which don't have one.
|
rss-bridge is a PHP project capable of generating ATOM feeds for websites which don't have one.
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ Requirements
|
|||||||
|
|
||||||
* PHP 5.6, e.g. `AddHandler application/x-httpd-php56 .php` in `.htaccess`
|
* PHP 5.6, e.g. `AddHandler application/x-httpd-php56 .php` in `.htaccess`
|
||||||
* `openssl` extension enabled in PHP config (`php.ini`)
|
* `openssl` extension enabled in PHP config (`php.ini`)
|
||||||
* `allow_url_fopen=1` in `php.ini`
|
* `curl` extension enabled in PHP config (`php.ini`)
|
||||||
|
|
||||||
Enabling/Disabling bridges
|
Enabling/Disabling bridges
|
||||||
===
|
===
|
||||||
|
187
bridges/AmazonPriceTrackerBridge.php
Normal file
187
bridges/AmazonPriceTrackerBridge.php
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||||
|
const MAINTAINER = 'captn3m0';
|
||||||
|
const NAME = 'Amazon Price Tracker';
|
||||||
|
const URI = 'https://www.amazon.com/';
|
||||||
|
const CACHE_TIMEOUT = 3600; // 1h
|
||||||
|
const DESCRIPTION = 'Tracks price for a single product on Amazon';
|
||||||
|
|
||||||
|
const PARAMETERS = array(
|
||||||
|
array(
|
||||||
|
'asin' => array(
|
||||||
|
'name' => 'ASIN',
|
||||||
|
'required' => true,
|
||||||
|
'exampleValue' => 'B071GB1VMQ',
|
||||||
|
// https://stackoverflow.com/a/12827734
|
||||||
|
'pattern' => 'B[\dA-Z]{9}|\d{9}(X|\d)',
|
||||||
|
),
|
||||||
|
'tld' => array(
|
||||||
|
'name' => 'Country',
|
||||||
|
'type' => 'list',
|
||||||
|
'required' => true,
|
||||||
|
'values' => array(
|
||||||
|
'Australia' => 'com.au',
|
||||||
|
'Brazil' => 'com.br',
|
||||||
|
'Canada' => 'ca',
|
||||||
|
'China' => 'cn',
|
||||||
|
'France' => 'fr',
|
||||||
|
'Germany' => 'de',
|
||||||
|
'India' => 'in',
|
||||||
|
'Italy' => 'it',
|
||||||
|
'Japan' => 'co.jp',
|
||||||
|
'Mexico' => 'com.mx',
|
||||||
|
'Netherlands' => 'nl',
|
||||||
|
'Spain' => 'es',
|
||||||
|
'United Kingdom' => 'co.uk',
|
||||||
|
'United States' => 'com',
|
||||||
|
),
|
||||||
|
'defaultValue' => 'com',
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
protected $title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates domain name given a amazon TLD
|
||||||
|
*/
|
||||||
|
private function getDomainName() {
|
||||||
|
return 'https://www.amazon.' . $this->getInput('tld');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates URI for a Amazon product page
|
||||||
|
*/
|
||||||
|
public function getURI() {
|
||||||
|
if (!is_null($this->getInput('asin'))) {
|
||||||
|
return $this->getDomainName() . '/dp/' . $this->getInput('asin') . '/';
|
||||||
|
}
|
||||||
|
return parent::getURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrapes the product title from the html page
|
||||||
|
* returns the default title if scraping fails
|
||||||
|
*/
|
||||||
|
private function getTitle($html) {
|
||||||
|
$titleTag = $html->find('#productTitle', 0);
|
||||||
|
|
||||||
|
if (!$titleTag) {
|
||||||
|
return $this->getDefaultTitle();
|
||||||
|
} else {
|
||||||
|
return trim(html_entity_decode($titleTag->innertext, ENT_QUOTES));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Title used by the feed if none could be found
|
||||||
|
*/
|
||||||
|
private function getDefaultTitle() {
|
||||||
|
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('asin');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns name for the feed
|
||||||
|
* Uses title (already scraped) if it has one
|
||||||
|
*/
|
||||||
|
public function getName() {
|
||||||
|
if (isset($this->title)) {
|
||||||
|
return $this->title;
|
||||||
|
} else {
|
||||||
|
return parent::getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseDynamicImage($attribute) {
|
||||||
|
$json = json_decode(html_entity_decode($attribute), true);
|
||||||
|
|
||||||
|
if ($json and count($json) > 0) {
|
||||||
|
return array_keys($json)[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a generated image tag for the product
|
||||||
|
*/
|
||||||
|
private function getImage($html) {
|
||||||
|
$imageSrc = $html->find('#main-image-container img', 0);
|
||||||
|
|
||||||
|
if ($imageSrc) {
|
||||||
|
$hiresImage = $imageSrc->getAttribute('data-old-hires');
|
||||||
|
$dynamicImageAttribute = $imageSrc->getAttribute('data-a-dynamic-image');
|
||||||
|
$image = $hiresImage ?: $this->parseDynamicImage($dynamicImageAttribute);
|
||||||
|
}
|
||||||
|
$image = $image ?: 'https://placekitten.com/200/300';
|
||||||
|
|
||||||
|
return <<<EOT
|
||||||
|
<img width="300" style="max-width:300;max-height:300" src="$image" alt="{$this->title}" />
|
||||||
|
EOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return \simple_html_dom object
|
||||||
|
* for the entire html of the product page
|
||||||
|
*/
|
||||||
|
private function getHtml() {
|
||||||
|
$uri = $this->getURI();
|
||||||
|
|
||||||
|
return getSimpleHTMLDOM($uri) ?: returnServerError('Could not request Amazon.');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function scrapePriceFromMetrics($html) {
|
||||||
|
$asinData = $html->find('#cerberus-data-metrics', 0);
|
||||||
|
|
||||||
|
// <div id="cerberus-data-metrics" style="display: none;"
|
||||||
|
// data-asin="B00WTHJ5SU" data-asin-price="14.99" data-asin-shipping="0"
|
||||||
|
// data-asin-currency-code="USD" data-substitute-count="-1" ... />
|
||||||
|
if ($asinData) {
|
||||||
|
return [
|
||||||
|
'price' => $asinData->getAttribute('data-asin-price'),
|
||||||
|
'currency' => $asinData->getAttribute('data-asin-currency-code'),
|
||||||
|
'shipping' => $asinData->getAttribute('data-asin-shipping')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function scrapePriceGeneric($html) {
|
||||||
|
$priceDiv = $html->find('span.offer-price', 0) ?: $html->find('.a-color-price', 0);
|
||||||
|
|
||||||
|
preg_match('/^\s*([A-Z]{3}|£|\$)\s?([\d.,]+)\s*$/', $priceDiv->plaintext, $matches);
|
||||||
|
|
||||||
|
if (count($matches) === 3) {
|
||||||
|
return [
|
||||||
|
'price' => $matches[2],
|
||||||
|
'currency' => $matches[1],
|
||||||
|
'shipping' => '0'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrape method for Amazon product page
|
||||||
|
* @return [type] [description]
|
||||||
|
*/
|
||||||
|
public function collectData() {
|
||||||
|
$html = $this->getHtml();
|
||||||
|
$this->title = $this->getTitle($html);
|
||||||
|
$imageTag = $this->getImage($html);
|
||||||
|
|
||||||
|
$data = $this->scrapePriceFromMetrics($html) ?: $this->scrapePriceGeneric($html);
|
||||||
|
|
||||||
|
$item = array(
|
||||||
|
'title' => $this->title,
|
||||||
|
'uri' => $this->getURI(),
|
||||||
|
'content' => "$imageTag<br/>Price: {$data['price']} {$data['currency']}",
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($data['shipping'] !== '0') {
|
||||||
|
$item['content'] .= "<br>Shipping: {$data['shipping']} {$data['currency']}</br>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
@@ -19,7 +19,7 @@ class BlaguesDeMerdeBridge extends BridgeAbstract {
|
|||||||
$item['content'] = trim($element->find('div.joke_text_contener', 0)->innertext);
|
$item['content'] = trim($element->find('div.joke_text_contener', 0)->innertext);
|
||||||
$uri = $temp[2]->href;
|
$uri = $temp[2]->href;
|
||||||
$item['uri'] = $uri;
|
$item['uri'] = $uri;
|
||||||
$item['title'] = substr($uri, (strrpos($uri, "/") + 1));
|
$item['title'] = substr($uri, (strrpos($uri, '/') + 1));
|
||||||
$date = $element->find('li.bdm_date', 0)->innertext;
|
$date = $element->find('li.bdm_date', 0)->innertext;
|
||||||
$time = mktime(0, 0, 0, substr($date, 3, 2), substr($date, 0, 2), substr($date, 6, 4));
|
$time = mktime(0, 0, 0, substr($date, 3, 2), substr($date, 0, 2), substr($date, 6, 4));
|
||||||
$item['timestamp'] = $time;
|
$item['timestamp'] = $time;
|
||||||
|
@@ -23,14 +23,14 @@ class CADBridge extends FeedExpander {
|
|||||||
if($html3 == false)
|
if($html3 == false)
|
||||||
return 'Daily comic not released yet';
|
return 'Daily comic not released yet';
|
||||||
|
|
||||||
$htmlpart = explode("/", $url);
|
$htmlpart = explode('/', $url);
|
||||||
|
|
||||||
switch ($htmlpart[3]) {
|
switch ($htmlpart[3]) {
|
||||||
case 'cad':
|
case 'cad':
|
||||||
preg_match_all("/http:\/\/cdn2\.cad-comic\.com\/comics\/cad-\S*png/", $html3, $url2);
|
preg_match_all('/http:\/\/cdn2\.cad-comic\.com\/comics\/cad-\S*png/', $html3, $url2);
|
||||||
break;
|
break;
|
||||||
case 'sillies':
|
case 'sillies':
|
||||||
preg_match_all("/http:\/\/cdn2\.cad-comic\.com\/comics\/sillies-\S*gif/", $html3, $url2);
|
preg_match_all('/http:\/\/cdn2\.cad-comic\.com\/comics\/sillies-\S*gif/', $html3, $url2);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return 'Daily comic not released yet';
|
return 'Daily comic not released yet';
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
93
bridges/ContainerLinuxReleasesBridge.php
Normal file
93
bridges/ContainerLinuxReleasesBridge.php
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
class ContainerLinuxReleasesBridge extends BridgeAbstract {
|
||||||
|
|
||||||
|
const MAINTAINER = 'captn3m0';
|
||||||
|
const NAME = 'Core OS Container Linux Releases Bridge';
|
||||||
|
const URI = 'https://coreos.com/releases/';
|
||||||
|
const DESCRIPTION = 'Returns the releases notes for Container Linux';
|
||||||
|
|
||||||
|
const STABLE = 'stable';
|
||||||
|
const BETA = 'beta';
|
||||||
|
const ALPHA = 'alpha';
|
||||||
|
|
||||||
|
const PARAMETERS = [
|
||||||
|
[
|
||||||
|
'channel' => [
|
||||||
|
'name' => 'Release Channel',
|
||||||
|
'type' => 'list',
|
||||||
|
'required' => true,
|
||||||
|
'defaultValue' => self::STABLE,
|
||||||
|
'values' => [
|
||||||
|
'Stable' => self::STABLE,
|
||||||
|
'Beta' => self::BETA,
|
||||||
|
'Alpha' => self::ALPHA,
|
||||||
|
],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getReleaseFeed($jsonUrl) {
|
||||||
|
$json = getContents($jsonUrl)
|
||||||
|
or returnServerError('Could not request Core OS Website.');
|
||||||
|
return json_decode($json, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
$data = $this->getReleaseFeed($this->getJsonUri());
|
||||||
|
|
||||||
|
foreach ($data as $releaseVersion => $release) {
|
||||||
|
$item = [];
|
||||||
|
|
||||||
|
$item['uri'] = "https://coreos.com/releases/#$releaseVersion";
|
||||||
|
$item['title'] = $releaseVersion;
|
||||||
|
|
||||||
|
$content = $release['release_notes'];
|
||||||
|
$content .= <<<EOT
|
||||||
|
|
||||||
|
Major Software:
|
||||||
|
* Kernel: {$release['major_software']['kernel'][0]}
|
||||||
|
* Docker: {$release['major_software']['docker'][0]}
|
||||||
|
* etcd: {$release['major_software']['etcd'][0]}
|
||||||
|
EOT;
|
||||||
|
$item['timestamp'] = strtotime($release['release_date']);
|
||||||
|
|
||||||
|
// Based on https://gist.github.com/jbroadway/2836900
|
||||||
|
// Links
|
||||||
|
$regex = '/\[([^\[]+)\]\(([^\)]+)\)/';
|
||||||
|
$replacement = '<a href=\'\2\'>\1</a>';
|
||||||
|
$item['content'] = preg_replace($regex, $replacement, $content);
|
||||||
|
|
||||||
|
// Headings
|
||||||
|
$regex = '/^(.*)\:\s?$/m';
|
||||||
|
$replacement = '<h3>\1</h3>';
|
||||||
|
$item['content'] = preg_replace($regex, $replacement, $item['content']);
|
||||||
|
|
||||||
|
// Lists
|
||||||
|
$regex = '/\n\s*[\*|\-](.*)/';
|
||||||
|
$item['content'] = preg_replace_callback ($regex, function($regs) {
|
||||||
|
$item = $regs[1];
|
||||||
|
return sprintf ('<ul><li>%s</li></ul>', trim ($item));
|
||||||
|
}, $item['content']);
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getJsonUri() {
|
||||||
|
$channel = $this->getInput('channel');
|
||||||
|
|
||||||
|
return "https://coreos.com/releases/releases-$channel.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getURI() {
|
||||||
|
return self::URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(){
|
||||||
|
if(!is_null($this->getInput('channel'))) {
|
||||||
|
return 'Container Linux Releases: ' . $this->getInput('channel') . ' Channel';
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getName();
|
||||||
|
}
|
||||||
|
}
|
@@ -25,7 +25,7 @@ class CopieDoubleBridge extends BridgeAbstract {
|
|||||||
} elseif(strpos($element->innertext, '/images/suivant.gif') === false) {
|
} elseif(strpos($element->innertext, '/images/suivant.gif') === false) {
|
||||||
$a = $element->find('a', 0);
|
$a = $element->find('a', 0);
|
||||||
$item['uri'] = self::URI . $a->href;
|
$item['uri'] = self::URI . $a->href;
|
||||||
$content = str_replace('src="/', 'src="/' . self::URI, $element->find("td", 0)->innertext);
|
$content = str_replace('src="/', 'src="/' . self::URI, $element->find('td', 0)->innertext);
|
||||||
$content = str_replace('href="/', 'href="' . self::URI, $content);
|
$content = str_replace('href="/', 'href="' . self::URI, $content);
|
||||||
$item['content'] = $content;
|
$item['content'] = $content;
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
|
@@ -11,7 +11,7 @@ class CourrierInternationalBridge extends BridgeAbstract {
|
|||||||
$html = getSimpleHTMLDOM(self::URI)
|
$html = getSimpleHTMLDOM(self::URI)
|
||||||
or returnServerError('Error.');
|
or returnServerError('Error.');
|
||||||
|
|
||||||
$element = $html->find("article");
|
$element = $html->find('article');
|
||||||
$article_count = 1;
|
$article_count = 1;
|
||||||
|
|
||||||
foreach($element as $article) {
|
foreach($element as $article) {
|
||||||
|
@@ -1,74 +0,0 @@
|
|||||||
<?php
|
|
||||||
class CpasbienBridge extends BridgeAbstract {
|
|
||||||
|
|
||||||
const MAINTAINER = 'lagaisse';
|
|
||||||
const NAME = 'Cpasbien Bridge';
|
|
||||||
const URI = 'http://www.cpasbien.cm';
|
|
||||||
const CACHE_TIMEOUT = 86400; // 24h
|
|
||||||
const DESCRIPTION = 'Returns latest torrents from a request query';
|
|
||||||
|
|
||||||
const PARAMETERS = array( array(
|
|
||||||
'q' => array(
|
|
||||||
'name' => 'Search',
|
|
||||||
'required' => true,
|
|
||||||
'title' => 'Type your search'
|
|
||||||
)
|
|
||||||
));
|
|
||||||
|
|
||||||
public function collectData(){
|
|
||||||
$request = str_replace(" ", "-", trim($this->getInput('q')));
|
|
||||||
$html = getSimpleHTMLDOM(self::URI . '/recherche/' . urlencode($request) . '.html')
|
|
||||||
or returnServerError('No results for this query.');
|
|
||||||
|
|
||||||
foreach($html->find('#gauche', 0)->find('div') as $episode) {
|
|
||||||
if($episode->getAttribute('class') == 'ligne0'
|
|
||||||
|| $episode->getAttribute('class') == 'ligne1') {
|
|
||||||
|
|
||||||
$urlepisode = $episode->find('a', 0)->getAttribute('href');
|
|
||||||
$htmlepisode = getSimpleHTMLDOMCached($urlepisode, 86400 * 366 * 30);
|
|
||||||
|
|
||||||
$item = array();
|
|
||||||
$item['author'] = $episode->find('a', 0)->text();
|
|
||||||
$item['title'] = $episode->find('a', 0)->text();
|
|
||||||
$item['pubdate'] = $this->getCachedDate($urlepisode);
|
|
||||||
$textefiche = $htmlepisode->find('#textefiche', 0)->find('p', 1);
|
|
||||||
|
|
||||||
if(isset($textefiche)) {
|
|
||||||
$item['content'] = $textefiche->text();
|
|
||||||
} else {
|
|
||||||
$p = $htmlepisode->find('#textefiche', 0)->find('p');
|
|
||||||
if(!empty($p)) {
|
|
||||||
$item['content'] = $htmlepisode->find('#textefiche', 0)->find('p', 0)->text();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$item['id'] = $episode->find('a', 0)->getAttribute('href');
|
|
||||||
$item['uri'] = self::URI . $htmlepisode->find('#telecharger', 0)->getAttribute('href');
|
|
||||||
$this->items[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(){
|
|
||||||
if(!is_null($this->getInput('q'))) {
|
|
||||||
return $this->getInput('q') . ' : ' . self::NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent::getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getCachedDate($url){
|
|
||||||
debugMessage('getting pubdate from url ' . $url . '');
|
|
||||||
|
|
||||||
// Initialize cache
|
|
||||||
$cache = Cache::create('FileCache');
|
|
||||||
$cache->setPath(CACHE_DIR . '/pages');
|
|
||||||
|
|
||||||
$params = [$url];
|
|
||||||
$cache->setParameters($params);
|
|
||||||
|
|
||||||
// Get cachefile timestamp
|
|
||||||
$time = $cache->getTime();
|
|
||||||
return ($time !== false ? $time : time());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -41,7 +41,7 @@ class DanbooruBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
$item = array();
|
$item = array();
|
||||||
$item['uri'] = $element->find('a', 0)->href;
|
$item['uri'] = $element->find('a', 0)->href;
|
||||||
$item['postid'] = (int)preg_replace("/[^0-9]/", '', $element->getAttribute(static::IDATTRIBUTE));
|
$item['postid'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE));
|
||||||
$item['timestamp'] = time();
|
$item['timestamp'] = time();
|
||||||
$thumbnailUri = $element->find('img', 0)->src;
|
$thumbnailUri = $element->find('img', 0)->src;
|
||||||
$item['tags'] = $this->getTags($element);
|
$item['tags'] = $this->getTags($element);
|
||||||
|
@@ -15,8 +15,13 @@ class DansTonChatBridge extends BridgeAbstract {
|
|||||||
foreach($html->find('div.item') as $element) {
|
foreach($html->find('div.item') as $element) {
|
||||||
$item = array();
|
$item = array();
|
||||||
$item['uri'] = $element->find('a', 0)->href;
|
$item['uri'] = $element->find('a', 0)->href;
|
||||||
$item['title'] = 'DansTonChat ' . $element->find('a', 1)->plaintext;
|
$titleContent = $element->find('h3 a', 0);
|
||||||
$item['content'] = $element->find('a', 0)->innertext;
|
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;
|
$this->items[] = $item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
class DealabsBridge extends BridgeAbstract {
|
class DealabsBridge extends PepperBridgeAbstract {
|
||||||
const NAME = 'Dealabs search bridge';
|
|
||||||
|
const NAME = 'Dealabs Bridge';
|
||||||
const URI = 'https://www.dealabs.com/';
|
const URI = 'https://www.dealabs.com/';
|
||||||
const DESCRIPTION = 'Return the Dealabs search result using keywords';
|
const DESCRIPTION = 'Affiche les Deals de Dealabs';
|
||||||
const MAINTAINER = 'sysadminstory';
|
const MAINTAINER = 'sysadminstory';
|
||||||
const PARAMETERS = array(
|
const PARAMETERS = array(
|
||||||
'Recherche par Mot(s) clé(s)' => array (
|
'Recherche par Mot(s) clé(s)' => array (
|
||||||
@@ -39,7 +40,7 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
),
|
),
|
||||||
|
|
||||||
'Deals par groupe' => array(
|
'Deals par groupe' => array(
|
||||||
'groupe' => array(
|
'group' => array(
|
||||||
'name' => 'Groupe',
|
'name' => 'Groupe',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'required' => 'true',
|
'required' => 'true',
|
||||||
@@ -61,10 +62,10 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
'Services divers' => 'services-divers',
|
'Services divers' => 'services-divers',
|
||||||
'Sports & plein air' => 'sports-plein-air',
|
'Sports & plein air' => 'sports-plein-air',
|
||||||
'Téléphonie' => 'telephonie',
|
'Téléphonie' => 'telephonie',
|
||||||
'Voyages & sorties' => 'voyages-sorties-restaurants'
|
'Voyages & sorties' => 'voyages-sorties-restaurants',
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
'ordre' => array(
|
'order' => array(
|
||||||
'name' => 'Trier par',
|
'name' => 'Trier par',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'required' => 'true',
|
'required' => 'true',
|
||||||
@@ -78,37 +79,99 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public $lang = array(
|
||||||
|
'bridge-uri' => SELF::URI,
|
||||||
|
'bridge-name' => SELF::NAME,
|
||||||
|
'context-keyword' => 'Recherche par Mot(s) clé(s)',
|
||||||
|
'context-group' => 'Deals par groupe',
|
||||||
|
'uri-group' => '/groupe/',
|
||||||
|
'request-error' => 'Could not request Dealabs',
|
||||||
|
'no-results' => 'Il n'y a rien à afficher pour le moment :(',
|
||||||
|
'relative-date-indicator' => array(
|
||||||
|
'il y a',
|
||||||
|
),
|
||||||
|
'price' => 'Prix',
|
||||||
|
'shipping' => 'Livraison',
|
||||||
|
'origin' => 'Origine',
|
||||||
|
'discount' => 'Réduction',
|
||||||
|
'title-keyword' => 'Recherche',
|
||||||
|
'title-group' => 'Groupe',
|
||||||
|
'local-months' => array(
|
||||||
|
'janvier',
|
||||||
|
'février',
|
||||||
|
'mars',
|
||||||
|
'avril',
|
||||||
|
'mai',
|
||||||
|
'juin',
|
||||||
|
'juillet',
|
||||||
|
'août',
|
||||||
|
'septembre',
|
||||||
|
'octobre',
|
||||||
|
'novembre',
|
||||||
|
'décembre'
|
||||||
|
),
|
||||||
|
'local-time-relative' => array(
|
||||||
|
'il y a ',
|
||||||
|
'min',
|
||||||
|
'h',
|
||||||
|
'jour',
|
||||||
|
'jours',
|
||||||
|
'mois',
|
||||||
|
'ans',
|
||||||
|
'et '
|
||||||
|
),
|
||||||
|
'date-prefixes' => array(
|
||||||
|
'Actualisé ',
|
||||||
|
),
|
||||||
|
'relative-date-alt-prefixes' => array(
|
||||||
|
'Actualisé ',
|
||||||
|
),
|
||||||
|
'relative-date-ignore-suffix' => array(
|
||||||
|
),
|
||||||
|
|
||||||
|
'localdeal' => array(
|
||||||
|
'Local',
|
||||||
|
'Pays d\'expédition'
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class PepperBridgeAbstract extends BridgeAbstract {
|
||||||
|
|
||||||
const CACHE_TIMEOUT = 3600;
|
const CACHE_TIMEOUT = 3600;
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
switch($this->queriedContext) {
|
switch($this->queriedContext) {
|
||||||
case 'Recherche par Mot(s) clé(s)':
|
case $this->i8n('context-keyword'):
|
||||||
return $this->collectDataMotsCles();
|
return $this->collectDataKeywords();
|
||||||
break;
|
break;
|
||||||
case 'Deals par groupe':
|
case $this->i8n('context-group'):
|
||||||
return $this->collectDataGroupe();
|
return $this->collectDataGroup();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Deal data from the choosen groupe in the choose order
|
* Get the Deal data from the choosen group in the choosed order
|
||||||
*/
|
*/
|
||||||
public function collectDataGroupe()
|
public function collectDataGroup()
|
||||||
{
|
{
|
||||||
|
|
||||||
$groupe = $this->getInput('groupe');
|
$group = $this->getInput('group');
|
||||||
$ordre = $this->getInput('ordre');
|
$order = $this->getInput('order');
|
||||||
|
|
||||||
$url = self::URI
|
$url = $this->i8n('bridge-uri')
|
||||||
. '/groupe/' . $groupe . $ordre;
|
. $this->i8n('uri-group') . $group . $order;
|
||||||
$this->collectDeals($url);
|
$this->collectDeals($url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Deal data from the choosen keywords and parameters
|
* Get the Deal data from the choosen keywords and parameters
|
||||||
*/
|
*/
|
||||||
public function collectDataMotsCles()
|
public function collectDataKeywords()
|
||||||
{
|
{
|
||||||
$q = $this->getInput('q');
|
$q = $this->getInput('q');
|
||||||
$hide_expired = $this->getInput('hide_expired');
|
$hide_expired = $this->getInput('hide_expired');
|
||||||
@@ -117,7 +180,7 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
$priceTo = $this->getInput('priceFrom');
|
$priceTo = $this->getInput('priceFrom');
|
||||||
|
|
||||||
/* Even if the original website uses POST with the search page, GET works too */
|
/* Even if the original website uses POST with the search page, GET works too */
|
||||||
$url = self::URI
|
$url = $this->i8n('bridge-uri')
|
||||||
. '/search/advanced?q='
|
. '/search/advanced?q='
|
||||||
. urlencode($q)
|
. urlencode($q)
|
||||||
. '&hide_expired='. $hide_expired
|
. '&hide_expired='. $hide_expired
|
||||||
@@ -138,8 +201,8 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
*/
|
*/
|
||||||
public function collectDeals($url){
|
public function collectDeals($url){
|
||||||
$html = getSimpleHTMLDOM($url)
|
$html = getSimpleHTMLDOM($url)
|
||||||
or returnServerError('Could not request Dealabs.');
|
or returnServerError($this->i8n('request-error'));
|
||||||
$list = $html->find('article');
|
$list = $html->find('article[id]');
|
||||||
|
|
||||||
// Deal Image Link CSS Selector
|
// Deal Image Link CSS Selector
|
||||||
$selectorImageLink = implode(
|
$selectorImageLink = implode(
|
||||||
@@ -148,7 +211,6 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
'cept-thread-image-link',
|
'cept-thread-image-link',
|
||||||
'imgFrame',
|
'imgFrame',
|
||||||
'imgFrame--noBorder',
|
'imgFrame--noBorder',
|
||||||
'box--all-i',
|
|
||||||
'thread-listImgCell',
|
'thread-listImgCell',
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -179,9 +241,7 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
' ', /* Notice this is a space! */
|
' ', /* Notice this is a space! */
|
||||||
array(
|
array(
|
||||||
'cept-description-container',
|
'cept-description-container',
|
||||||
'overflow--wrap-break',
|
'overflow--wrap-break'
|
||||||
'size--all-s',
|
|
||||||
'size--fromW3-m',
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -191,7 +251,6 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
array(
|
array(
|
||||||
'size--all-s',
|
'size--all-s',
|
||||||
'flex',
|
'flex',
|
||||||
'flex--wrap',
|
|
||||||
'flex--justify-e',
|
'flex--justify-e',
|
||||||
'flex--grow-1',
|
'flex--grow-1',
|
||||||
)
|
)
|
||||||
@@ -199,40 +258,44 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
// If there is no results, we don't parse the content because it display some random deals
|
// 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);
|
$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 :(') {
|
if ($noresult != null && strpos($noresult->plaintext, $this->i8n('no-results')) !== false) {
|
||||||
$this->items = array();
|
$this->items = array();
|
||||||
} else {
|
} else {
|
||||||
foreach($list as $deal) {
|
foreach ($list as $deal) {
|
||||||
$item = array();
|
$item = array();
|
||||||
$item['uri'] = $deal->find('div[class=threadGrid-title]', 0)->find('a', 0)->href;
|
$item['uri'] = $deal->find('div[class=threadGrid-title]', 0)->find('a', 0)->href;
|
||||||
$item['title'] = $deal->find('a[class*='. $selectorLink .']', 0
|
$item['title'] = $deal->find('a[class*='. $selectorLink .']', 0
|
||||||
)->plaintext;
|
)->plaintext;
|
||||||
$item['author'] = $deal->find('span.thread-username', 0)->plaintext;
|
$item['author'] = $deal->find('span.thread-username', 0)->plaintext;
|
||||||
$item['content'] = '<table><tr><td><a href="'
|
$item['content'] = '<table><tr><td><a href="'
|
||||||
. $deal->find(
|
. $deal->find(
|
||||||
'a[class*='. $selectorImageLink .']', 0)->href
|
'a[class*='. $selectorImageLink .']', 0)->href
|
||||||
. '"><img src="'
|
. '"><img src="'
|
||||||
. $this->getImage($deal)
|
. $this->getImage($deal)
|
||||||
. '"/></td><td><h2><a href="'
|
. '"/></td><td><h2><a href="'
|
||||||
. $deal->find('a[class*='. $selectorLink .']', 0)->href
|
. $deal->find('a[class*='. $selectorLink .']', 0)->href
|
||||||
. '">'
|
. '">'
|
||||||
. $deal->find('a[class*='. $selectorLink .']', 0)->innertext
|
. $deal->find('a[class*='. $selectorLink .']', 0)->innertext
|
||||||
. '</a></h2>'
|
. '</a></h2>'
|
||||||
. $this->getPrix($deal)
|
. $this->getPrice($deal)
|
||||||
. $this->getReduction($deal)
|
. $this->getDiscount($deal)
|
||||||
. $this->getExpedition($deal)
|
. $this->getShipsFrom($deal)
|
||||||
. $this->getLivraison($deal)
|
. $this->getShippingCost($deal)
|
||||||
. $this->getOrigine($deal)
|
. $this->GetSource($deal)
|
||||||
. $deal->find('div[class='. $selectorDescription .']', 0)->innertext
|
. $deal->find('div[class*='. $selectorDescription .']', 0)->innertext
|
||||||
. '</td><td>'
|
. '</td><td>'
|
||||||
. $deal->find('div[class='. $selectorHot .']', 0)->children(0)->outertext
|
. $deal->find('div[class='. $selectorHot .']', 0)->children(0)->outertext
|
||||||
. '</td></table>';
|
. '</td></table>';
|
||||||
$dealDateDiv = $deal->find('div[class='. $selectorDate .']', 0)
|
$dealDateDiv = $deal->find('div[class*='. $selectorDate .']', 0)
|
||||||
->find('span[class=hide--toW3]');
|
->find('span[class=hide--toW3]');
|
||||||
$itemDate = end($dealDateDiv)->plaintext;
|
$itemDate = end($dealDateDiv)->plaintext;
|
||||||
if(substr( $itemDate, 0, 6 ) === 'il y a') {
|
// In case of a Local deal, there is no date, but we can use
|
||||||
|
// this case for other reason (like date not in the last field)
|
||||||
|
if ($this->contains($itemDate, $this->i8n('localdeal'))) {
|
||||||
|
$item['timestamp'] = time();
|
||||||
|
} else if ($this->contains($itemDate, $this->i8n('relative-date-indicator'))) {
|
||||||
$item['timestamp'] = $this->relativeDateToTimestamp($itemDate);
|
$item['timestamp'] = $this->relativeDateToTimestamp($itemDate);
|
||||||
} else {
|
} else {
|
||||||
$item['timestamp'] = $this->parseDate($itemDate);
|
$item['timestamp'] = $this->parseDate($itemDate);
|
||||||
}
|
}
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
@@ -240,15 +303,29 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the string $str contains any of the string of the array $arr
|
||||||
|
* @return boolean true if the string matched anything otherwise false
|
||||||
|
*/
|
||||||
|
private function contains($str, array $arr)
|
||||||
|
{
|
||||||
|
foreach ($arr as $a) {
|
||||||
|
if (stripos($str, $a) !== false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Price from a Deal if it exists
|
* Get the Price from a Deal if it exists
|
||||||
* @return string String of the deal price
|
* @return string String of the deal price
|
||||||
*/
|
*/
|
||||||
private function getPrix($deal)
|
private function getPrice($deal)
|
||||||
{
|
{
|
||||||
if($deal->find(
|
if ($deal->find(
|
||||||
'span[class*=thread-price]', 0) != null) {
|
'span[class*=thread-price]', 0) != null) {
|
||||||
return '<div>Prix : '
|
return '<div>'.$this->i8n('price') .' : '
|
||||||
. $deal->find(
|
. $deal->find(
|
||||||
'span[class*=thread-price]', 0
|
'span[class*=thread-price]', 0
|
||||||
)->plaintext
|
)->plaintext
|
||||||
@@ -263,17 +340,17 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
* Get the Shipping costs from a Deal if it exists
|
* Get the Shipping costs from a Deal if it exists
|
||||||
* @return string String of the deal shipping Cost
|
* @return string String of the deal shipping Cost
|
||||||
*/
|
*/
|
||||||
private function getLivraison($deal)
|
private function getShippingCost($deal)
|
||||||
{
|
{
|
||||||
if($deal->find('span[class*=cept-shipping-price]', 0) != null) {
|
if ($deal->find('span[class*=cept-shipping-price]', 0) != null) {
|
||||||
if($deal->find('span[class*=cept-shipping-price]', 0)->children(0) != null) {
|
if ($deal->find('span[class*=cept-shipping-price]', 0)->children(0) != null) {
|
||||||
return '<div>Livraison : '
|
return '<div>'. $this->i8n('shipping') .' : '
|
||||||
. $deal->find('span[class*=cept-shipping-price]', 0)->children(0)->innertext
|
. $deal->find('span[class*=cept-shipping-price]', 0)->children(0)->innertext
|
||||||
. '</div>';
|
. '</div>';
|
||||||
} else {
|
} else {
|
||||||
return '<div>Livraison : '
|
return '<div>'. $this->i8n('shipping') .' : '
|
||||||
. $deal->find('span[class*=cept-shipping-price]', 0)->innertext
|
. $deal->find('span[class*=cept-shipping-price]', 0)->innertext
|
||||||
. '</div>';
|
. '</div>';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
@@ -284,10 +361,10 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
* Get the source of a Deal if it exists
|
* Get the source of a Deal if it exists
|
||||||
* @return string String of the deal source
|
* @return string String of the deal source
|
||||||
*/
|
*/
|
||||||
private function getOrigine($deal)
|
private function GetSource($deal)
|
||||||
{
|
{
|
||||||
if($deal->find('a[class=text--color-greyShade]', 0) != null) {
|
if ($deal->find('a[class=text--color-greyShade]', 0) != null) {
|
||||||
return '<div>Origine : '
|
return '<div>'. $this->i8n('origin') .' : '
|
||||||
. $deal->find('a[class=text--color-greyShade]', 0)->outertext
|
. $deal->find('a[class=text--color-greyShade]', 0)->outertext
|
||||||
. '</div>';
|
. '</div>';
|
||||||
} else {
|
} else {
|
||||||
@@ -299,15 +376,21 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
* Get the original Price and discout from a Deal if it exists
|
* Get the original Price and discout from a Deal if it exists
|
||||||
* @return string String of the deal original price and discount
|
* @return string String of the deal original price and discount
|
||||||
*/
|
*/
|
||||||
private function getReduction($deal)
|
private function getDiscount($deal)
|
||||||
{
|
{
|
||||||
if($deal->find('span[class*=mute--text text--lineThrough]', 0) != null) {
|
if ($deal->find('span[class*=mute--text text--lineThrough]', 0) != null) {
|
||||||
return '<div>Réduction : <span style="text-decoration: line-through;">'
|
$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>'. $this->i8n('discount') .' : <span style="text-decoration: line-through;">'
|
||||||
. $deal->find(
|
. $deal->find(
|
||||||
'span[class*=mute--text text--lineThrough]', 0
|
'span[class*=mute--text text--lineThrough]', 0
|
||||||
)->plaintext
|
)->plaintext
|
||||||
. '</span> '
|
. '</span> '
|
||||||
. $deal->find('span[class=space--ml-1 size--all-l size--fromW3-xl]', 0)->plaintext
|
. $discount
|
||||||
. '</div>';
|
. '</div>';
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
@@ -320,7 +403,6 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
*/
|
*/
|
||||||
private function getImage($deal)
|
private function getImage($deal)
|
||||||
{
|
{
|
||||||
|
|
||||||
$selectorLazy = implode(
|
$selectorLazy = implode(
|
||||||
' ', /* Notice this is a space! */
|
' ', /* Notice this is a space! */
|
||||||
array(
|
array(
|
||||||
@@ -334,7 +416,7 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$selectorPlain = implode(
|
$selectorPlain = implode(
|
||||||
' ', /* Notice this is a space! */
|
' ', /* Notice this is a space! */
|
||||||
array(
|
array(
|
||||||
'thread-image',
|
'thread-image',
|
||||||
@@ -344,21 +426,21 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
'cept-thread-img'
|
'cept-thread-img'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if($deal->find('img[class='. $selectorLazy .']', 0) != null) {
|
if ($deal->find('img[class='. $selectorLazy .']', 0) != null) {
|
||||||
return json_decode(
|
return json_decode(
|
||||||
html_entity_decode(
|
html_entity_decode(
|
||||||
$deal->find('img[class='. $selectorLazy .']', 0)
|
$deal->find('img[class='. $selectorLazy .']', 0)
|
||||||
->getAttribute('data-lazy-img')))->{'src'};
|
->getAttribute('data-lazy-img')))->{'src'};
|
||||||
} else {
|
} else {
|
||||||
return $deal->find('img[class='. $selectorPlain .']', 0 )->src;
|
return $deal->find('img[class*='. $selectorPlain .']', 0 )->src;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the originating country from a Deal if it existsa
|
* Get the originating country from a Deal if it exists
|
||||||
* @return string String of the deal originating country
|
* @return string String of the deal originating country
|
||||||
*/
|
*/
|
||||||
private function getExpedition($deal)
|
private function getShipsFrom($deal)
|
||||||
{
|
{
|
||||||
$selector = implode(
|
$selector = implode(
|
||||||
' ', /* Notice this is a space! */
|
' ', /* Notice this is a space! */
|
||||||
@@ -369,7 +451,7 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
'text--color-greyShade'
|
'text--color-greyShade'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if($deal->find('span[class='. $selector .']', 0) != null) {
|
if ($deal->find('span[class='. $selector .']', 0) != null) {
|
||||||
return '<div>'
|
return '<div>'
|
||||||
. $deal->find('span[class='. $selector .']', 0)->children(2)->plaintext
|
. $deal->find('span[class='. $selector .']', 0)->children(2)->plaintext
|
||||||
. '</div>';
|
. '</div>';
|
||||||
@@ -379,25 +461,12 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms a French date into a timestam
|
* Transforms a local date into a timestamp
|
||||||
* @return int timestamp of the input date
|
* @return int timestamp of the input date
|
||||||
*/
|
*/
|
||||||
private function parseDate($string)
|
private function parseDate($string)
|
||||||
{
|
{
|
||||||
$month_fr = array(
|
$month_local = $this->i8n('local-months');
|
||||||
'janvier',
|
|
||||||
'février',
|
|
||||||
'mars',
|
|
||||||
'avril',
|
|
||||||
'mai',
|
|
||||||
'juin',
|
|
||||||
'juillet',
|
|
||||||
'août',
|
|
||||||
'septembre',
|
|
||||||
'octobre',
|
|
||||||
'novembre',
|
|
||||||
'décembre'
|
|
||||||
);
|
|
||||||
$month_en = array(
|
$month_en = array(
|
||||||
'January',
|
'January',
|
||||||
'February',
|
'February',
|
||||||
@@ -412,11 +481,18 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
'November',
|
'November',
|
||||||
'December'
|
'December'
|
||||||
);
|
);
|
||||||
$date_str = trim(str_replace($month_fr, $month_en, $string));
|
|
||||||
|
|
||||||
if(!preg_match('/[0-9]{4}/', $string)) {
|
// A date can be prfixed with some words, we remove theme
|
||||||
|
$string = $this->removeDatePrefixes($string);
|
||||||
|
// We translate the local months name in the english one
|
||||||
|
$date_str = trim(str_replace($month_local, $month_en, $string));
|
||||||
|
|
||||||
|
// If the date does not contain any year, we add the current year
|
||||||
|
if (!preg_match('/[0-9]{4}/', $string)) {
|
||||||
$date_str .= ' ' . date('Y');
|
$date_str .= ' ' . date('Y');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the Hour and minutes
|
||||||
$date_str .= ' 00:00';
|
$date_str .= ' 00:00';
|
||||||
|
|
||||||
$date = DateTime::createFromFormat('j F Y H:i', $date_str);
|
$date = DateTime::createFromFormat('j F Y H:i', $date_str);
|
||||||
@@ -424,21 +500,41 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms a relate French date into a timestam
|
* Remove the prefix of a date if it has one
|
||||||
|
* @return the date without prefiux
|
||||||
|
*/
|
||||||
|
private function removeDatePrefixes($string)
|
||||||
|
{
|
||||||
|
$string = str_replace($this->i8n('date-prefixes'), array(), $string);
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the suffix of a relative date if it has one
|
||||||
|
* @return the relative date without suffixes
|
||||||
|
*/
|
||||||
|
private function removeRelativeDateSuffixes($string)
|
||||||
|
{
|
||||||
|
if (count($this->i8n('relative-date-ignore-suffix')) > 0) {
|
||||||
|
$string = preg_replace($this->i8n('relative-date-ignore-suffix'), '', $string);
|
||||||
|
}
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms a relative local date into a timestamp
|
||||||
* @return int timestamp of the input date
|
* @return int timestamp of the input date
|
||||||
*/
|
*/
|
||||||
private function relativeDateToTimestamp($str) {
|
private function relativeDateToTimestamp($str) {
|
||||||
$date = new DateTime();
|
$date = new DateTime();
|
||||||
$search = array(
|
|
||||||
'il y a ',
|
// In case of update date, replace it by the regular relative date first word
|
||||||
'min',
|
$str = str_replace($this->i8n('relative-date-alt-prefixes'), $this->i8n('local-time-relative')[0], $str);
|
||||||
'h',
|
|
||||||
'jour',
|
$str = $this->removeRelativeDateSuffixes($str);
|
||||||
'jours',
|
|
||||||
'mois',
|
$search = $this->i8n('local-time-relative');
|
||||||
'ans',
|
|
||||||
'et '
|
|
||||||
);
|
|
||||||
$replace = array(
|
$replace = array(
|
||||||
'-',
|
'-',
|
||||||
'minute',
|
'minute',
|
||||||
@@ -453,18 +549,38 @@ class DealabsBridge extends BridgeAbstract {
|
|||||||
return $date->getTimestamp();
|
return $date->getTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the RSS Feed title according to the parameters
|
||||||
|
* @return string the RSS feed Tiyle
|
||||||
|
*/
|
||||||
public function getName(){
|
public function getName(){
|
||||||
switch($this->queriedContext) {
|
switch($this->queriedContext) {
|
||||||
case 'Recherche par Mot(s) clé(s)':
|
case $this->i8n('context-keyword'):
|
||||||
return self::NAME . ' - Recherche : '. $this->getInput('q');
|
return $this->i8n('bridge-name') . ' - '. $this->i8n('title-keyword') .' : '. $this->getInput('q');
|
||||||
break;
|
break;
|
||||||
case 'Deals par groupe':
|
case $this->i8n('context-group'):
|
||||||
$values = self::PARAMETERS['Deals par groupe']['groupe']['values'];
|
$values = $this->getParameters()[$this->i8n('context-group')]['group']['values'];
|
||||||
$groupe = array_search($this->getInput('groupe'), $values);
|
$group = array_search($this->getInput('group'), $values);
|
||||||
return self::NAME . ' - Groupe : '. $groupe;
|
return $this->i8n('bridge-name') . ' - '. $this->i8n('title-group'). ' : '. $group;
|
||||||
break;
|
break;
|
||||||
default: // Return default value
|
default: // Return default value
|
||||||
return self::NAME;
|
return static::NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is some "localisation" function that returns the needed content using
|
||||||
|
* the "$lang" class variable in the local class
|
||||||
|
* @return various the local content needed
|
||||||
|
*/
|
||||||
|
public function i8n($key)
|
||||||
|
{
|
||||||
|
if (array_key_exists($key, $this->lang)) {
|
||||||
|
return $this->lang[$key];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -35,11 +35,11 @@ class DemoBridge extends BridgeAbstract {
|
|||||||
public function collectData(){
|
public function collectData(){
|
||||||
|
|
||||||
$item = array();
|
$item = array();
|
||||||
$item['author'] = "Me!";
|
$item['author'] = 'Me!';
|
||||||
$item['title'] = "Test";
|
$item['title'] = 'Test';
|
||||||
$item['content'] = "Awesome content !";
|
$item['content'] = 'Awesome content !';
|
||||||
$item['id'] = "Lalala";
|
$item['id'] = 'Lalala';
|
||||||
$item['uri'] = "http://example.com/test";
|
$item['uri'] = 'http://example.com/test';
|
||||||
|
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
}
|
}
|
||||||
|
@@ -4,143 +4,163 @@ class DemonoidBridge extends BridgeAbstract {
|
|||||||
const MAINTAINER = 'metaMMA';
|
const MAINTAINER = 'metaMMA';
|
||||||
const NAME = 'Demonoid';
|
const NAME = 'Demonoid';
|
||||||
const URI = 'https://www.demonoid.pw/';
|
const URI = 'https://www.demonoid.pw/';
|
||||||
const DESCRIPTION = 'Returns results for the keywords (in all categories or
|
const DESCRIPTION = 'Returns results from search';
|
||||||
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(
|
const PARAMETERS = array(array(
|
||||||
'q' => array(
|
'q' => array(
|
||||||
'name' => 'keywords/user ID/category, separated by semicolons',
|
'name' => 'keywords',
|
||||||
'exampleValue' => 'first list;second list;…',
|
'exampleValue' => 'keyword1 keyword2…',
|
||||||
'required' => true
|
'required' => true,
|
||||||
),
|
),
|
||||||
'crit' => array(
|
'category' => array(
|
||||||
|
'name' => 'Category',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'name' => 'Feed type',
|
|
||||||
'values' => array(
|
'values' => array(
|
||||||
'search' => 'search',
|
'All' => 0,
|
||||||
'category' => 'cat',
|
'Movies' => 1,
|
||||||
'user' => 'usr'
|
'Music' => 2,
|
||||||
|
'TV' => 3,
|
||||||
|
'Games' => 4,
|
||||||
|
'Applications' => 5,
|
||||||
|
'Pictures' => 8,
|
||||||
|
'Anime' => 9,
|
||||||
|
'Comics' => 10,
|
||||||
|
'Books' => 11,
|
||||||
|
'Audiobooks' => 17
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
), array(
|
||||||
'catCheck' => array(
|
'catOnly' => array(
|
||||||
'type' => 'checkbox',
|
'name' => 'Category',
|
||||||
'name' => 'Specify category for keyword search ?',
|
'type' => 'list',
|
||||||
),
|
'values' => array(
|
||||||
'cat' => array(
|
'All' => 0,
|
||||||
'name' => 'Category number',
|
'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() {
|
public function collectData() {
|
||||||
|
|
||||||
$catBool = $this->getInput('catCheck');
|
if(!empty($this->getInput('q'))) {
|
||||||
if($catBool) {
|
|
||||||
$catNum = $this->getInput('cat');
|
$html = getSimpleHTMLDOM(
|
||||||
|
self::URI .
|
||||||
|
'files/?category=' .
|
||||||
|
rawurlencode($this->getInput('category')) .
|
||||||
|
'&subcategory=All&quality=All&seeded=2&external=2&query=' .
|
||||||
|
urlencode($this->getInput('q')) .
|
||||||
|
'&uid=0&sort='
|
||||||
|
) or returnServerError('Could not request Demonoid.');
|
||||||
|
|
||||||
|
} elseif(!empty($this->getInput('catOnly'))) {
|
||||||
|
|
||||||
|
$html = getSimpleHTMLDOM(
|
||||||
|
self::URI .
|
||||||
|
'files/?uid=0&category=' .
|
||||||
|
rawurlencode($this->getInput('catOnly')) .
|
||||||
|
'&subcategory=0&language=0&seeded=2&quality=0&query=&sort='
|
||||||
|
) or returnServerError('Could not request Demonoid.');
|
||||||
|
|
||||||
|
} elseif(!empty($this->getInput('userid'))) {
|
||||||
|
|
||||||
|
$html = getSimpleHTMLDOM(
|
||||||
|
self::URI .
|
||||||
|
'files/?uid=' .
|
||||||
|
rawurlencode($this->getInput('userid')) .
|
||||||
|
'&seeded=2'
|
||||||
|
) or returnServerError('Could not request Demonoid.');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
returnServerError('Invalid parameters !');
|
||||||
}
|
}
|
||||||
$critList = $this->getInput('crit');
|
|
||||||
|
|
||||||
$keywordsList = explode(';', $this->getInput('q'));
|
if(preg_match('~No torrents found~', $html)) {
|
||||||
foreach($keywordsList as $keywords) {
|
return;
|
||||||
switch($critList) {
|
}
|
||||||
case 'search':
|
|
||||||
if($catBool == false) {
|
$table = $html->find('td[class=ctable_content_no_pad]', 0);
|
||||||
$html = file_get_contents(
|
$cursorCount = 4;
|
||||||
self::URI .
|
$elementCount = 0;
|
||||||
'files/?category=0&subcategory=All&quality=All&seeded=2&external=2&query=' .
|
while($elementCount != 40) {
|
||||||
urlencode($keywords) . #not rawurlencode so space -> '+'
|
$elementCount++;
|
||||||
'&uid=0&sort='
|
$currentElement = $table->find('tr', $cursorCount);
|
||||||
) or returnServerError('Could not request Demonoid.');
|
if(preg_match('~items total~', $currentElement)) {
|
||||||
} else {
|
break;
|
||||||
$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;
|
|
||||||
}
|
}
|
||||||
|
$item = array();
|
||||||
if(preg_match('~No torrents found~', $html)) {
|
//Do we have a date ?
|
||||||
returnServerError('No result for query ' . $keywords);
|
if(preg_match('~Added.*?(.*)~', $currentElement->plaintext, $dateStr)) {
|
||||||
}
|
|
||||||
|
|
||||||
$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])) {
|
if(preg_match('~today~', $dateStr[0])) {
|
||||||
date_default_timezone_set('UTC');
|
date_default_timezone_set('UTC');
|
||||||
$timestamp = mktime(0, 0, 0, gmdate('n'), gmdate('j'), gmdate('Y'));
|
$timestamp = mktime(0, 0, 0, gmdate('n'), gmdate('j'), gmdate('Y'));
|
||||||
} else {
|
} else {
|
||||||
preg_match('~(?<=ed on ).*\d+~', $dateStr[0], $fullDateStr);
|
preg_match('~(?<=ed on ).*\d+~', $currentElement->plaintext, $fullDateStr);
|
||||||
date_default_timezone_set('UTC');
|
date_default_timezone_set('UTC');
|
||||||
$dateObj = strptime($fullDateStr[0], '%A, %b %d, %Y');
|
$dateObj = strptime($fullDateStr[0], '%A, %b %d, %Y');
|
||||||
$timestamp = mktime(0, 0, 0, $dateObj['tm_mon'] + 1, $dateObj['tm_mday'], 1900 + $dateObj['tm_year']);
|
$timestamp = mktime(0, 0, 0, $dateObj['tm_mon'] + 1, $dateObj['tm_mday'], 1900 + $dateObj['tm_year']);
|
||||||
}
|
}
|
||||||
|
$cursorCount++;
|
||||||
$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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
class EZTVBridge extends BridgeAbstract {
|
class EZTVBridge extends BridgeAbstract {
|
||||||
|
|
||||||
const MAINTAINER = "alexAubin";
|
const MAINTAINER = 'alexAubin';
|
||||||
const NAME = 'EZTV';
|
const NAME = 'EZTV';
|
||||||
const URI = 'https://eztv.ch/';
|
const URI = 'https://eztv.ch/';
|
||||||
const DESCRIPTION = 'Returns list of *recent* torrents for a specific show
|
const DESCRIPTION = 'Returns list of *recent* torrents for a specific show
|
||||||
@@ -23,15 +23,15 @@ on EZTV. Get showID from URLs in https://eztv.ch/shows/showID/show-full-name.';
|
|||||||
$relativeDays = 0;
|
$relativeDays = 0;
|
||||||
$relativeHours = 0;
|
$relativeHours = 0;
|
||||||
|
|
||||||
foreach(explode(" ", $relativeReleaseTime) as $relativeTimeElement) {
|
foreach(explode(' ', $relativeReleaseTime) as $relativeTimeElement) {
|
||||||
if(substr($relativeTimeElement, -1) == "d") $relativeDays = substr($relativeTimeElement, 0, -1);
|
if(substr($relativeTimeElement, -1) == 'd') $relativeDays = substr($relativeTimeElement, 0, -1);
|
||||||
if(substr($relativeTimeElement, -1) == "h") $relativeHours = substr($relativeTimeElement, 0, -1);
|
if(substr($relativeTimeElement, -1) == 'h') $relativeHours = substr($relativeTimeElement, 0, -1);
|
||||||
}
|
}
|
||||||
return mktime(date('h') - $relativeHours, 0, 0, date('m'), date('d') - $relativeDays, date('Y'));
|
return mktime(date('h') - $relativeHours, 0, 0, date('m'), date('d') - $relativeDays, date('Y'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop on show ids
|
// Loop on show ids
|
||||||
$showList = explode(",", $this->getInput('i'));
|
$showList = explode(',', $this->getInput('i'));
|
||||||
foreach($showList as $showID) {
|
foreach($showList as $showID) {
|
||||||
|
|
||||||
// Get show page
|
// Get show page
|
||||||
|
147
bridges/ElloBridge.php
Normal file
147
bridges/ElloBridge.php
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<?php
|
||||||
|
class ElloBridge extends BridgeAbstract {
|
||||||
|
|
||||||
|
const MAINTAINER = 'teromene';
|
||||||
|
const NAME = 'Ello Bridge';
|
||||||
|
const URI = 'https://ello.co/';
|
||||||
|
const CACHE_TIMEOUT = 4800; //2hours
|
||||||
|
const DESCRIPTION = 'Returns the newest posts for Ello';
|
||||||
|
|
||||||
|
const PARAMETERS = array(
|
||||||
|
'By User' => array(
|
||||||
|
'u' => array(
|
||||||
|
'name' => 'Username',
|
||||||
|
'required' => true,
|
||||||
|
'title' => 'Username'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'Search' => array(
|
||||||
|
's' => array(
|
||||||
|
'name' => 'Search',
|
||||||
|
'required' => true,
|
||||||
|
'title' => 'Search'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
|
||||||
|
$header = array(
|
||||||
|
'Authorization: Bearer ' . $this->getAPIKey()
|
||||||
|
);
|
||||||
|
|
||||||
|
if(!empty($this->getInput('u'))) {
|
||||||
|
$postData = getContents(self::URI . 'api/v2/users/~' . urlencode($this->getInput('u')) . '/posts', $header) or
|
||||||
|
returnServerError('Unable to query Ello API.');
|
||||||
|
} else {
|
||||||
|
$postData = getContents(self::URI . 'api/v2/posts?terms=' . urlencode($this->getInput('s')), $header) or
|
||||||
|
returnServerError('Unable to query Ello API.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$postData = json_decode($postData);
|
||||||
|
$count = 0;
|
||||||
|
foreach($postData->posts as $post) {
|
||||||
|
|
||||||
|
$item = array();
|
||||||
|
$item['author'] = $this->getUsername($post, $postData);
|
||||||
|
$item['timestamp'] = strtotime($post->created_at);
|
||||||
|
$item['title'] = strip_tags($this->findText($post->summary));
|
||||||
|
$item['content'] = $this->getPostContent($post->body);
|
||||||
|
$item['enclosures'] = $this->getEnclosures($post, $postData);
|
||||||
|
$item['uri'] = self::URI . $item['author'] . '/post/' . $post->token;
|
||||||
|
$content = $post->body;
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
$count += 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -95,7 +95,7 @@ EOD;
|
|||||||
. $pageID
|
. $pageID
|
||||||
. '&cursor={"card_id"%3A"videos"%2C"has_next_page"%3Atrue}&surface=mobile_page_home&unit_count=8';
|
. '&cursor={"card_id"%3A"videos"%2C"has_next_page"%3Atrue}&surface=mobile_page_home&unit_count=8';
|
||||||
|
|
||||||
$fileContent = file_get_contents($requestString);
|
$fileContent = getContents($requestString);
|
||||||
|
|
||||||
$articleIndex = 0;
|
$articleIndex = 0;
|
||||||
$maxArticle = 3;
|
$maxArticle = 3;
|
||||||
@@ -103,19 +103,19 @@ EOD;
|
|||||||
$html = $this->buildContent($fileContent);
|
$html = $this->buildContent($fileContent);
|
||||||
$author = $this->getInput('u');
|
$author = $this->getInput('u');
|
||||||
|
|
||||||
foreach($html->find("article") as $content) {
|
foreach($html->find('article') as $content) {
|
||||||
|
|
||||||
$item = array();
|
$item = array();
|
||||||
|
|
||||||
$item['uri'] = "http://touch.facebook.com"
|
$item['uri'] = 'http://touch.facebook.com'
|
||||||
. $content->find("div[class='_52jc _5qc4 _24u0 _36xo']", 0)->find("a", 0)->getAttribute("href");
|
. $content->find("div[class='_52jc _5qc4 _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href');
|
||||||
|
|
||||||
if($content->find("header", 0) !== null) {
|
if($content->find('header', 0) !== null) {
|
||||||
$content->find("header", 0)->innertext = "";
|
$content->find('header', 0)->innertext = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if($content->find("footer", 0) !== null) {
|
if($content->find('footer', 0) !== null) {
|
||||||
$content->find("footer", 0)->innertext = "";
|
$content->find('footer', 0)->innertext = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
//Remove html nodes, keep only img, links, basic formatting
|
//Remove html nodes, keep only img, links, basic formatting
|
||||||
@@ -168,7 +168,7 @@ EOD;
|
|||||||
$regex = implode(
|
$regex = implode(
|
||||||
'',
|
'',
|
||||||
array(
|
array(
|
||||||
"/timeline_unit",
|
'/timeline_unit',
|
||||||
"\\\\\\\\u00253A1",
|
"\\\\\\\\u00253A1",
|
||||||
"\\\\\\\\u00253A([0-9]*)",
|
"\\\\\\\\u00253A([0-9]*)",
|
||||||
"\\\\\\\\u00253A([0-9]*)",
|
"\\\\\\\\u00253A([0-9]*)",
|
||||||
@@ -182,29 +182,29 @@ EOD;
|
|||||||
return implode(
|
return implode(
|
||||||
'',
|
'',
|
||||||
array(
|
array(
|
||||||
"https://touch.facebook.com/pages_reaction_units/more/?page_id=",
|
'https://touch.facebook.com/pages_reaction_units/more/?page_id=',
|
||||||
$pageID,
|
$pageID,
|
||||||
"&cursor=%7B%22timeline_cursor%22%3A%22timeline_unit%3A1%3A",
|
'&cursor=%7B%22timeline_cursor%22%3A%22timeline_unit%3A1%3A',
|
||||||
$result[1],
|
$result[1],
|
||||||
"%3A",
|
'%3A',
|
||||||
$result[2],
|
$result[2],
|
||||||
"%3A",
|
'%3A',
|
||||||
$result[3],
|
$result[3],
|
||||||
"%3A",
|
'%3A',
|
||||||
$result[4],
|
$result[4],
|
||||||
"%22%2C%22timeline_section_cursor%22%3A%7B%7D%2C%22",
|
'%22%2C%22timeline_section_cursor%22%3A%7B%7D%2C%22',
|
||||||
"has_next_page%22%3Atrue%7D&surface=mobile_page_home&unit_count=3"
|
'has_next_page%22%3Atrue%7D&surface=mobile_page_home&unit_count=3'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Builds the HTML from the encoded JS that Facebook provides.
|
//Builds the HTML from the encoded JS that Facebook provides.
|
||||||
private function buildContent($pageContent){
|
private function buildContent($pageContent){
|
||||||
|
// The html ends with:
|
||||||
$regex = "/\\\"html\\\":\\\"(.*?)\\\",\\\"replace/";
|
// /div>","replaceifexists
|
||||||
|
$regex = '/\\"html\\":(\".+\/div>"),"replace/';
|
||||||
preg_match($regex, $pageContent, $result);
|
preg_match($regex, $pageContent, $result);
|
||||||
|
return str_get_html(html_entity_decode(json_decode($result[1])));
|
||||||
return str_get_html(html_entity_decode(json_decode('"' . $result[1] . '"')));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ EOD;
|
|||||||
|
|
||||||
$ctx = stream_context_create(array(
|
$ctx = stream_context_create(array(
|
||||||
'http' => array(
|
'http' => array(
|
||||||
'user_agent' => "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0",
|
'user_agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0',
|
||||||
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
|
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -222,12 +222,12 @@ EOD;
|
|||||||
$a = file_get_contents($pageURL, 0, $ctx);
|
$a = file_get_contents($pageURL, 0, $ctx);
|
||||||
|
|
||||||
//First request to get the cookie
|
//First request to get the cookie
|
||||||
$cookies = "";
|
$cookies = '';
|
||||||
foreach($http_response_header as $hdr) {
|
foreach($http_response_header as $hdr) {
|
||||||
if(strpos($hdr, "Set-Cookie") !== false) {
|
if(strpos($hdr, 'Set-Cookie') !== false) {
|
||||||
$cLine = explode(":", $hdr)[1];
|
$cLine = explode(':', $hdr)[1];
|
||||||
$cLine = explode(";", $cLine)[0];
|
$cLine = explode(';', $cLine)[0];
|
||||||
$cookies .= ";" . $cLine;
|
$cookies .= ';' . $cLine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +239,7 @@ EOD;
|
|||||||
|
|
||||||
$context = stream_context_create(array(
|
$context = stream_context_create(array(
|
||||||
'http' => array(
|
'http' => array(
|
||||||
'user_agent' => "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0",
|
'user_agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0',
|
||||||
'header' => 'Cookie: ' . $cookies
|
'header' => 'Cookie: ' . $cookies
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -247,12 +247,12 @@ EOD;
|
|||||||
|
|
||||||
$pageContent = file_get_contents($page, 0, $context);
|
$pageContent = file_get_contents($page, 0, $context);
|
||||||
|
|
||||||
if(strpos($pageContent, "signup-button") != false) {
|
if(strpos($pageContent, 'signup-button') != false) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Get the page ID if we don't have a captcha
|
//Get the page ID if we don't have a captcha
|
||||||
$regex = "/page_id=([0-9]*)&/";
|
$regex = '/page_id=([0-9]*)&/';
|
||||||
preg_match($regex, $pageContent, $matches);
|
preg_match($regex, $pageContent, $matches);
|
||||||
|
|
||||||
if(count($matches) > 0) {
|
if(count($matches) > 0) {
|
||||||
@@ -260,7 +260,7 @@ EOD;
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Get the page ID if we do have a captcha
|
//Get the page ID if we do have a captcha
|
||||||
$regex = "/\"pageID\":\"([0-9]*)\"/";
|
$regex = '/"pageID":"([0-9]*)"/';
|
||||||
preg_match($regex, $pageContent, $matches);
|
preg_match($regex, $pageContent, $matches);
|
||||||
|
|
||||||
return $matches[1];
|
return $matches[1];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,34 +1,257 @@
|
|||||||
<?php
|
<?php
|
||||||
class FacebookBridge extends BridgeAbstract {
|
class FacebookBridge extends BridgeAbstract {
|
||||||
|
|
||||||
const MAINTAINER = 'teromene';
|
const MAINTAINER = 'teromene, logmanoriginal';
|
||||||
const NAME = 'Facebook';
|
const NAME = 'Facebook';
|
||||||
const URI = 'https://www.facebook.com/';
|
const URI = 'https://www.facebook.com/';
|
||||||
const CACHE_TIMEOUT = 300; // 5min
|
const CACHE_TIMEOUT = 300; // 5min
|
||||||
const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
|
const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
|
||||||
please insert the parameter as follow : myExamplePage/132621766841117';
|
please insert the parameter as follow : myExamplePage/132621766841117';
|
||||||
|
|
||||||
const PARAMETERS = array( array(
|
const PARAMETERS = array(
|
||||||
'u' => array(
|
'User' => array(
|
||||||
'name' => 'Username',
|
'u' => array(
|
||||||
'required' => true
|
'name' => 'Username',
|
||||||
),
|
'required' => true
|
||||||
'media_type' => array(
|
|
||||||
'name' => 'Media type',
|
|
||||||
'type' => 'list',
|
|
||||||
'required' => false,
|
|
||||||
'values' => array(
|
|
||||||
'All' => 'all',
|
|
||||||
'Video' => 'video',
|
|
||||||
'No Video' => 'novideo'
|
|
||||||
),
|
),
|
||||||
'defaultValue' => 'all'
|
'media_type' => array(
|
||||||
|
'name' => 'Media type',
|
||||||
|
'type' => 'list',
|
||||||
|
'required' => false,
|
||||||
|
'values' => array(
|
||||||
|
'All' => 'all',
|
||||||
|
'Video' => 'video',
|
||||||
|
'No Video' => 'novideo'
|
||||||
|
),
|
||||||
|
'defaultValue' => 'all'
|
||||||
|
),
|
||||||
|
'skip_reviews' => array(
|
||||||
|
'name' => 'Skip reviews',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'required' => false,
|
||||||
|
'defaultValue' => false,
|
||||||
|
'title' => 'Feed includes reviews when checked'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'Group' => array(
|
||||||
|
'g' => array(
|
||||||
|
'name' => 'Group',
|
||||||
|
'type' => 'text',
|
||||||
|
'required' => true,
|
||||||
|
'exampleValue' => 'https://www.facebook.com/groups/743149642484225',
|
||||||
|
'title' => 'Insert group name or facebook group URL'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
));
|
);
|
||||||
|
|
||||||
private $authorName = '';
|
private $authorName = '';
|
||||||
|
private $groupName = '';
|
||||||
|
|
||||||
public function collectData(){
|
public function getURI() {
|
||||||
|
$uri = self::URI;
|
||||||
|
|
||||||
|
switch($this->queriedContext) {
|
||||||
|
|
||||||
|
case 'Group':
|
||||||
|
$uri .= 'groups/' . $this->sanitizeGroup(filter_var($this->getInput('g'), FILTER_SANITIZE_URL));
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uri .= '?_fb_noscript=1';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
|
||||||
|
switch($this->queriedContext) {
|
||||||
|
|
||||||
|
case 'Group':
|
||||||
|
$this->collectGroupData();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'User':
|
||||||
|
$this->collectUserData();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
returnClientError('Unknown context: "' . $this->queriedContext . '"!');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Group
|
||||||
|
|
||||||
|
private function collectGroupData() {
|
||||||
|
|
||||||
|
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n");
|
||||||
|
|
||||||
|
$html = getSimpleHTMLDOM($this->getURI(), $header)
|
||||||
|
or returnServerError('Failed loading facebook page: ' . $this->getURI());
|
||||||
|
|
||||||
|
if(!$this->isPublicGroup($html)) {
|
||||||
|
returnClientError('This group is not public! RSS-Bridge only supports public groups!');
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultLinkTo($html, substr(self::URI, 0, strlen(self::URI) - 1));
|
||||||
|
|
||||||
|
$this->groupName = $this->extractGroupName($html);
|
||||||
|
|
||||||
|
$posts = $html->find('div.userContentWrapper')
|
||||||
|
or returnServerError('Failed finding posts!');
|
||||||
|
|
||||||
|
foreach($posts as $post) {
|
||||||
|
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
$item['uri'] = $this->extractGroupURI($post);
|
||||||
|
$item['title'] = $this->extractGroupTitle($post);
|
||||||
|
$item['author'] = $this->extractGroupAuthor($post);
|
||||||
|
$item['content'] = $this->extractGroupContent($post);
|
||||||
|
$item['timestamp'] = $this->extractGroupTimestamp($post);
|
||||||
|
$item['enclosures'] = $this->extractGroupEnclosures($post);
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sanitizeGroup($group) {
|
||||||
|
|
||||||
|
if(filter_var(
|
||||||
|
$group,
|
||||||
|
FILTER_VALIDATE_URL,
|
||||||
|
FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED)) {
|
||||||
|
// User provided a URL
|
||||||
|
|
||||||
|
$urlparts = parse_url($group);
|
||||||
|
|
||||||
|
if($urlparts['host'] !== parse_url(self::URI)['host']
|
||||||
|
&& 'www.' . $urlparts['host'] !== parse_url(self::URI)['host']) {
|
||||||
|
|
||||||
|
returnClientError('The host you provided is invalid! Received "'
|
||||||
|
. $urlparts['host']
|
||||||
|
. '", expected "'
|
||||||
|
. parse_url(self::URI)['host']
|
||||||
|
. '"!');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return explode('/', $urlparts['path'])[2];
|
||||||
|
|
||||||
|
} elseif(strpos($group, '/') !== false) {
|
||||||
|
returnClientError('The group you provided is invalid: ' . $group);
|
||||||
|
} else {
|
||||||
|
return $group;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isPublicGroup($html) {
|
||||||
|
|
||||||
|
// Facebook redirects to the groups about page for non-public groups
|
||||||
|
$about = $html->find('#pagelet_group_about', 0);
|
||||||
|
|
||||||
|
return !($about);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractGroupName($html) {
|
||||||
|
|
||||||
|
$ogtitle = $html->find('meta[property="og:title"]', 0)
|
||||||
|
or returnServerError('Unable to find group title!');
|
||||||
|
|
||||||
|
return htmlspecialchars_decode($ogtitle->content, ENT_QUOTES);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractGroupURI($post) {
|
||||||
|
|
||||||
|
$elements = $post->find('a')
|
||||||
|
or returnServerError('Unable to find URI!');
|
||||||
|
|
||||||
|
foreach($elements as $anchor) {
|
||||||
|
|
||||||
|
// Find the one that is a permalink
|
||||||
|
if(strpos($anchor->href, 'permalink') !== false) {
|
||||||
|
return $anchor->href;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractGroupContent($post) {
|
||||||
|
|
||||||
|
$content = $post->find('div.userContent', 0)
|
||||||
|
or returnServerError('Unable to find user content!');
|
||||||
|
|
||||||
|
return $content->innertext . $content->next_sibling()->innertext;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractGroupTimestamp($post) {
|
||||||
|
|
||||||
|
$element = $post->find('abbr[data-utime]', 0)
|
||||||
|
or returnServerError('Unable to find timestamp!');
|
||||||
|
|
||||||
|
return $element->getAttribute('data-utime');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractGroupAuthor($post) {
|
||||||
|
|
||||||
|
$element = $post->find('img', 0)
|
||||||
|
or returnServerError('Unable to find author information!');
|
||||||
|
|
||||||
|
return $element->{'aria-label'};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractGroupEnclosures($post) {
|
||||||
|
|
||||||
|
$elements = $post->find('div.userContent', 0)->next_sibling()->find('img');
|
||||||
|
|
||||||
|
$enclosures = array();
|
||||||
|
|
||||||
|
foreach($elements as $enclosure) {
|
||||||
|
$enclosures[] = $enclosure->src;
|
||||||
|
}
|
||||||
|
|
||||||
|
return empty($enclosures) ? null : $enclosures;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractGroupTitle($post) {
|
||||||
|
|
||||||
|
$element = $post->find('h5', 0)
|
||||||
|
or returnServerError('Unable to find title!');
|
||||||
|
|
||||||
|
if(strpos($element->plaintext, 'shared') === false) {
|
||||||
|
|
||||||
|
$content = strip_tags($this->extractGroupContent($post));
|
||||||
|
|
||||||
|
return $this->extractGroupAuthor($post)
|
||||||
|
. ' posted: '
|
||||||
|
. substr(
|
||||||
|
$content,
|
||||||
|
0,
|
||||||
|
strpos(wordwrap($content, 64), "\n")
|
||||||
|
)
|
||||||
|
. '...';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $element->plaintext;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private function collectUserData(){
|
||||||
|
|
||||||
//Extract a string using start and end delimiters
|
//Extract a string using start and end delimiters
|
||||||
function extractFromDelimiters($string, $start, $end){
|
function extractFromDelimiters($string, $start, $end){
|
||||||
@@ -95,7 +318,7 @@ class FacebookBridge extends BridgeAbstract {
|
|||||||
if (isset($_SESSION['captcha_fields'], $_SESSION['captcha_action'])) {
|
if (isset($_SESSION['captcha_fields'], $_SESSION['captcha_action'])) {
|
||||||
$captcha_action = $_SESSION['captcha_action'];
|
$captcha_action = $_SESSION['captcha_action'];
|
||||||
$captcha_fields = $_SESSION['captcha_fields'];
|
$captcha_fields = $_SESSION['captcha_fields'];
|
||||||
$captcha_fields['captcha_response'] = preg_replace("/[^a-zA-Z0-9]+/", "", $_POST['captcha_response']);
|
$captcha_fields['captcha_response'] = preg_replace('/[^a-zA-Z0-9]+/', '', $_POST['captcha_response']);
|
||||||
|
|
||||||
$header = array("Content-type:
|
$header = array("Content-type:
|
||||||
application/x-www-form-urlencoded\r\nReferer: $captcha_action\r\nCookie: noscript=1\r\n");
|
application/x-www-form-urlencoded\r\nReferer: $captcha_action\r\nCookie: noscript=1\r\n");
|
||||||
@@ -120,17 +343,44 @@ application/x-www-form-urlencoded\r\nReferer: $captcha_action\r\nCookie: noscrip
|
|||||||
if(is_null($html)) {
|
if(is_null($html)) {
|
||||||
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n");
|
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n");
|
||||||
|
|
||||||
// First character cannot be a forward slash
|
// Check if the user provided a fully qualified URL
|
||||||
if(strpos($this->getInput('u'), "/") === 0) {
|
if (filter_var($this->getInput('u'), FILTER_VALIDATE_URL)) {
|
||||||
returnClientError('Remove leading slash "/" from the username!');
|
|
||||||
}
|
$urlparts = parse_url($this->getInput('u'));
|
||||||
|
|
||||||
|
if($urlparts['host'] !== parse_url(self::URI)['host']) {
|
||||||
|
returnClientError('The host you provided is invalid! Received "'
|
||||||
|
. $urlparts['host']
|
||||||
|
. '", expected "'
|
||||||
|
. parse_url(self::URI)['host']
|
||||||
|
. '"!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!array_key_exists('path', $urlparts)
|
||||||
|
|| $urlparts['path'] === '/') {
|
||||||
|
returnClientError('The URL you provided doesn\'t contain the user name!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = explode('/', $urlparts['path'])[1];
|
||||||
|
|
||||||
|
$html = getSimpleHTMLDOM(self::URI . urlencode($user) . '?_fb_noscript=1', $header)
|
||||||
|
or returnServerError('No results for this query.');
|
||||||
|
|
||||||
if(!strpos($this->getInput('u'), "/")) {
|
|
||||||
$html = getSimpleHTMLDOM(self::URI . urlencode($this->getInput('u')) . '?_fb_noscript=1', $header)
|
|
||||||
or returnServerError('No results for this query.');
|
|
||||||
} else {
|
} else {
|
||||||
$html = getSimpleHTMLDOM(self::URI . 'pages/' . $this->getInput('u') . '?_fb_noscript=1', $header)
|
|
||||||
or returnServerError('No results for this query.');
|
// First character cannot be a forward slash
|
||||||
|
if(strpos($this->getInput('u'), '/') === 0) {
|
||||||
|
returnClientError('Remove leading slash "/" from the username!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!strpos($this->getInput('u'), '/')) {
|
||||||
|
$html = getSimpleHTMLDOM(self::URI . urlencode($this->getInput('u')) . '?_fb_noscript=1', $header)
|
||||||
|
or returnServerError('No results for this query.');
|
||||||
|
} else {
|
||||||
|
$html = getSimpleHTMLDOM(self::URI . 'pages/' . $this->getInput('u') . '?_fb_noscript=1', $header)
|
||||||
|
or returnServerError('No results for this query.');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +414,12 @@ EOD;
|
|||||||
}
|
}
|
||||||
|
|
||||||
//No captcha? We can carry on retrieving page contents :)
|
//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
|
$element = $html
|
||||||
->find('#pagelet_timeline_main_column')[0]
|
->find('#pagelet_timeline_main_column')[0]
|
||||||
->children(0)
|
->children(0)
|
||||||
@@ -189,6 +445,12 @@ EOD;
|
|||||||
$posts = array($cell);
|
$posts = array($cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optionally skip reviews
|
||||||
|
if($this->getInput('skip_reviews')
|
||||||
|
&& !is_null($cell->find('#review_composer_container', 0))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
foreach($posts as $post) {
|
foreach($posts as $post) {
|
||||||
// Check media type
|
// Check media type
|
||||||
switch($this->getInput('media_type')) {
|
switch($this->getInput('media_type')) {
|
||||||
@@ -259,7 +521,7 @@ EOD;
|
|||||||
);
|
);
|
||||||
|
|
||||||
//Retrieve date of the post
|
//Retrieve date of the post
|
||||||
$date = $post->find("abbr")[0];
|
$date = $post->find('abbr')[0];
|
||||||
if(isset($date) && $date->hasAttribute('data-utime')) {
|
if(isset($date) && $date->hasAttribute('data-utime')) {
|
||||||
$date = $date->getAttribute('data-utime');
|
$date = $date->getAttribute('data-utime');
|
||||||
} else {
|
} else {
|
||||||
@@ -290,9 +552,22 @@ EOD;
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getName(){
|
public function getName(){
|
||||||
if(!empty($this->authorName)) {
|
|
||||||
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName
|
switch($this->queriedContext) {
|
||||||
. ' - Facebook Bridge';
|
|
||||||
|
case 'User':
|
||||||
|
if(!empty($this->authorName)) {
|
||||||
|
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName
|
||||||
|
. ' - Facebook Bridge';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Group':
|
||||||
|
if(!empty($this->groupName)) {
|
||||||
|
return $this->groupName . ' - Facebook Bridge';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::getName();
|
return parent::getName();
|
||||||
|
@@ -8,17 +8,22 @@ class FierPandaBridge extends BridgeAbstract {
|
|||||||
const DESCRIPTION = 'Returns latest articles from Fier Panda.';
|
const DESCRIPTION = 'Returns latest articles from Fier Panda.';
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
|
|
||||||
$html = getSimpleHTMLDOM(self::URI)
|
$html = getSimpleHTMLDOM(self::URI)
|
||||||
or returnServerError('Could not request Fier Panda.');
|
or returnServerError('Could not request Fier Panda.');
|
||||||
|
|
||||||
foreach($html->find('div.container-content article') as $element) {
|
defaultLinkTo($html, static::URI);
|
||||||
|
|
||||||
|
foreach($html->find('article') as $article) {
|
||||||
|
|
||||||
$item = array();
|
$item = array();
|
||||||
$item['uri'] = $this->getURI() . $element->find('a', 0)->href;
|
|
||||||
$item['title'] = trim($element->find('h1 a', 0)->innertext);
|
$item['uri'] = $article->find('a', 0)->href;
|
||||||
// Remove the link at the end of the article
|
$item['title'] = $article->find('a', 0)->title;
|
||||||
$element->find('p a', 0)->outertext = '';
|
|
||||||
$item['content'] = $element->find('p', 0)->innertext;
|
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,11 +26,34 @@ class FilterBridge extends FeedExpander {
|
|||||||
),
|
),
|
||||||
'defaultValue' => 'permit',
|
'defaultValue' => 'permit',
|
||||||
),
|
),
|
||||||
|
'title_from_content' => array(
|
||||||
|
'name' => 'Generate title from content',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'required' => false,
|
||||||
|
)
|
||||||
));
|
));
|
||||||
|
|
||||||
protected function parseItem($newItem){
|
protected function parseItem($newItem){
|
||||||
$item = parent::parseItem($newItem);
|
$item = parent::parseItem($newItem);
|
||||||
|
|
||||||
|
if($this->getInput('title_from_content') && array_key_exists('content', $item)) {
|
||||||
|
|
||||||
|
$content = str_get_html($item['content']);
|
||||||
|
|
||||||
|
$pos = strpos($item['content'], ' ', 50);
|
||||||
|
|
||||||
|
$item['title'] = substr(
|
||||||
|
$content->plaintext,
|
||||||
|
0,
|
||||||
|
$pos
|
||||||
|
);
|
||||||
|
|
||||||
|
if(strlen($content->plaintext) >= $pos) {
|
||||||
|
$item['title'] .= '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
switch(true) {
|
switch(true) {
|
||||||
case $this->getFilterType() === 'permit':
|
case $this->getFilterType() === 'permit':
|
||||||
if (preg_match($this->getFilter(), $item['title'])) {
|
if (preg_match($this->getFilter(), $item['title'])) {
|
||||||
|
@@ -30,30 +30,76 @@ class FlickrBridge extends BridgeAbstract {
|
|||||||
'title' => 'Insert username (as shown in the address bar)',
|
'title' => 'Insert username (as shown in the address bar)',
|
||||||
'exampleValue' => 'flickr'
|
'exampleValue' => 'flickr'
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
|
|
||||||
switch($this->queriedContext) {
|
switch($this->queriedContext) {
|
||||||
|
|
||||||
case 'Explore':
|
case 'Explore':
|
||||||
$key = 'photos';
|
$filter = 'photo-lite-models';
|
||||||
$html = getSimpleHTMLDOM(self::URI . 'explore')
|
$html = getSimpleHTMLDOM(self::URI . 'explore')
|
||||||
or returnServerError('Could not request Flickr.');
|
or returnServerError('Could not request Flickr.');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'By keyword':
|
case 'By keyword':
|
||||||
$key = 'photos';
|
$filter = 'photo-lite-models';
|
||||||
$html = getSimpleHTMLDOM(self::URI . 'search/?q=' . urlencode($this->getInput('q')) . '&s=rec')
|
$html = getSimpleHTMLDOM(self::URI . 'search/?q=' . urlencode($this->getInput('q')) . '&s=rec')
|
||||||
or returnServerError('No results for this query.');
|
or returnServerError('No results for this query.');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'By username':
|
case 'By username':
|
||||||
$key = 'photoPageList';
|
$filter = 'photo-models';
|
||||||
$html = getSimpleHTMLDOM(self::URI . 'photos/' . urlencode($this->getInput('u')))
|
$html = getSimpleHTMLDOM(self::URI . 'photos/' . urlencode($this->getInput('u')))
|
||||||
or returnServerError('Requested username can\'t be found.');
|
or returnServerError('Requested username can\'t be found.');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
returnClientError('Invalid context: ' . $this->queriedContext);
|
returnClientError('Invalid context: ' . $this->queriedContext);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$model_json = $this->extractJsonModel($html);
|
||||||
|
$photo_models = $this->getPhotoModels($model_json, $filter);
|
||||||
|
|
||||||
|
foreach($photo_models as $model) {
|
||||||
|
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
/* Author name depends on scope. On a keyword search the
|
||||||
|
* author is part of the picture data. On a username search
|
||||||
|
* the author is part of the owner data.
|
||||||
|
*/
|
||||||
|
if(array_key_exists('username', $model)) {
|
||||||
|
$item['author'] = $model['username'];
|
||||||
|
} elseif (array_key_exists('owner', reset($model_json)[0])) {
|
||||||
|
$item['author'] = reset($model_json)[0]['owner']['username'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$item['title'] = (array_key_exists('title', $model) ? $model['title'] : 'Untitled');
|
||||||
|
$item['uri'] = self::URI . 'photo.gne?id=' . $model['id'];
|
||||||
|
|
||||||
|
$description = (array_key_exists('description', $model) ? $model['description'] : '');
|
||||||
|
|
||||||
|
$item['content'] = '<a href="'
|
||||||
|
. $item['uri']
|
||||||
|
. '"><img src="'
|
||||||
|
. $this->extractContentImage($model)
|
||||||
|
. '" style="max-width: 640px; max-height: 480px;"/></a><br><p>'
|
||||||
|
. $description
|
||||||
|
. '</p>';
|
||||||
|
|
||||||
|
$item['enclosures'] = $this->extractEnclosures($model);
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractJsonModel($html) {
|
||||||
|
|
||||||
// Find SCRIPT containing JSON data
|
// Find SCRIPT containing JSON data
|
||||||
$model = $html->find('.modelExport', 0);
|
$model = $html->find('.modelExport', 0);
|
||||||
$model_text = $model->innertext;
|
$model_text = $model->innertext;
|
||||||
@@ -62,59 +108,79 @@ class FlickrBridge extends BridgeAbstract {
|
|||||||
$start = strpos($model_text, 'modelExport:') + strlen('modelExport:');
|
$start = strpos($model_text, 'modelExport:') + strlen('modelExport:');
|
||||||
$end = strpos($model_text, 'auth:') - strlen('auth:');
|
$end = strpos($model_text, 'auth:') - strlen('auth:');
|
||||||
|
|
||||||
// Dissect JSON data and remove trailing comma
|
// Extract JSON data, remove trailing comma
|
||||||
$model_text = trim(substr($model_text, $start, $end - $start));
|
$model_text = trim(substr($model_text, $start, $end - $start));
|
||||||
$model_text = substr($model_text, 0, strlen($model_text) - 1);
|
$model_text = substr($model_text, 0, strlen($model_text) - 1);
|
||||||
|
|
||||||
$model_json = json_decode($model_text, true);
|
return json_decode($model_text, true);
|
||||||
|
|
||||||
foreach($html->find('.photo-list-photo-view') as $element) {
|
|
||||||
// Get the styles
|
|
||||||
$style = explode(';', $element->style);
|
|
||||||
|
|
||||||
// Get the background-image style
|
|
||||||
$backgroundImage = explode(':', end($style));
|
|
||||||
|
|
||||||
// URI type : url(//cX.staticflickr.com/X/XXXXX/XXXXXXXXX.jpg)
|
|
||||||
$imageURI = trim(str_replace(['url(', ')'], '', end($backgroundImage)));
|
|
||||||
|
|
||||||
// Get the image ID
|
|
||||||
$imageURIs = explode('_', basename($imageURI));
|
|
||||||
$imageID = reset($imageURIs);
|
|
||||||
|
|
||||||
// Use JSON data to build items
|
|
||||||
foreach(reset($model_json)[0][$key]['_data'] as $element) {
|
|
||||||
if($element['id'] === $imageID) {
|
|
||||||
$item = array();
|
|
||||||
|
|
||||||
/* Author name depends on scope. On a keyword search the
|
|
||||||
* author is part of the picture data. On a username search
|
|
||||||
* the author is part of the owner data.
|
|
||||||
*/
|
|
||||||
if(array_key_exists('username', $element)) {
|
|
||||||
$item['author'] = $element['username'];
|
|
||||||
} elseif (array_key_exists('owner', reset($model_json)[0])) {
|
|
||||||
$item['author'] = reset($model_json)[0]['owner']['username'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$item['title'] = (array_key_exists('title', $element) ? $element['title'] : 'Untitled');
|
|
||||||
$item['uri'] = self::URI . 'photo.gne?id=' . $imageID;
|
|
||||||
|
|
||||||
$description = (array_key_exists('description', $element) ? $element['description'] : '');
|
|
||||||
|
|
||||||
$item['content'] = '<a href="'
|
|
||||||
. $item['uri']
|
|
||||||
. '"><img src="'
|
|
||||||
. $imageURI
|
|
||||||
. '" /></a><br><p>'
|
|
||||||
. $description
|
|
||||||
. '</p>';
|
|
||||||
|
|
||||||
$this->items[] = $item;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getPhotoModels($json, $filter) {
|
||||||
|
|
||||||
|
// The JSON model contains a "legend" array, where each element contains
|
||||||
|
// the path to an element in the "main" object
|
||||||
|
$photo_models = array();
|
||||||
|
|
||||||
|
foreach($json['legend'] as $legend) {
|
||||||
|
|
||||||
|
$photo_model = $json['main'];
|
||||||
|
|
||||||
|
foreach($legend as $element) { // Traverse tree
|
||||||
|
$photo_model = $photo_model[$element];
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are only interested in content
|
||||||
|
if($photo_model['_flickrModelRegistry'] === $filter) {
|
||||||
|
$photo_models[] = $photo_model;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $photo_models;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractEnclosures($model) {
|
||||||
|
|
||||||
|
$areas = array();
|
||||||
|
|
||||||
|
foreach($model['sizes'] as $size) {
|
||||||
|
$areas[$size['width'] * $size['height']] = $size['url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array($this->fixURL(max($areas)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractContentImage($model) {
|
||||||
|
|
||||||
|
$areas = array();
|
||||||
|
$limit = 320 * 240;
|
||||||
|
|
||||||
|
foreach($model['sizes'] as $size) {
|
||||||
|
|
||||||
|
$image_area = $size['width'] * $size['height'];
|
||||||
|
|
||||||
|
if($image_area >= $limit) {
|
||||||
|
$areas[$image_area] = $size['url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fixURL(min($areas));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fixURL($url) {
|
||||||
|
|
||||||
|
// For some reason the image URLs don't include the protocol (https)
|
||||||
|
if(strpos($url, '//') === 0) {
|
||||||
|
$url = 'https:' . $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $url;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -15,47 +15,47 @@ class FootitoBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
$content = trim($element->innertext);
|
$content = trim($element->innertext);
|
||||||
$content = str_replace(
|
$content = str_replace(
|
||||||
"<img",
|
'<img',
|
||||||
"<img style='float : left;'",
|
"<img style='float : left;'",
|
||||||
$content );
|
$content );
|
||||||
|
|
||||||
$content = str_replace(
|
$content = str_replace(
|
||||||
"class=\"logo\"",
|
'class="logo"',
|
||||||
"style='float : left;'",
|
"style='float : left;'",
|
||||||
$content );
|
$content );
|
||||||
|
|
||||||
$content = str_replace(
|
$content = str_replace(
|
||||||
"class=\"contenu\"",
|
'class="contenu"',
|
||||||
"style='margin-left : 60px;'",
|
"style='margin-left : 60px;'",
|
||||||
$content );
|
$content );
|
||||||
|
|
||||||
$content = str_replace(
|
$content = str_replace(
|
||||||
"class=\"responsive-comment\"",
|
'class="responsive-comment"',
|
||||||
"style='border-top : 1px #DDD solid; background-color : white; padding : 10px;'",
|
"style='border-top : 1px #DDD solid; background-color : white; padding : 10px;'",
|
||||||
$content );
|
$content );
|
||||||
|
|
||||||
$content = str_replace(
|
$content = str_replace(
|
||||||
"class=\"jaime\"",
|
'class="jaime"',
|
||||||
"style='display : none;'",
|
"style='display : none;'",
|
||||||
$content );
|
$content );
|
||||||
|
|
||||||
$content = str_replace(
|
$content = str_replace(
|
||||||
"class=\"auteur-event responsive\"",
|
'class="auteur-event responsive"',
|
||||||
"style='display : none;'",
|
"style='display : none;'",
|
||||||
$content );
|
$content );
|
||||||
|
|
||||||
$content = str_replace(
|
$content = str_replace(
|
||||||
"class=\"report-abuse-button\"",
|
'class="report-abuse-button"',
|
||||||
"style='display : none;'",
|
"style='display : none;'",
|
||||||
$content );
|
$content );
|
||||||
|
|
||||||
$content = str_replace(
|
$content = str_replace(
|
||||||
"class=\"reaction clearfix\"",
|
'class="reaction clearfix"',
|
||||||
"style='margin : 10px 0px; padding : 5px; border-bottom : 1px #DDD solid;'",
|
"style='margin : 10px 0px; padding : 5px; border-bottom : 1px #DDD solid;'",
|
||||||
$content );
|
$content );
|
||||||
|
|
||||||
$content = str_replace(
|
$content = str_replace(
|
||||||
"class=\"infos\"",
|
'class="infos"',
|
||||||
"style='font-size : 0.7em;'",
|
"style='font-size : 0.7em;'",
|
||||||
$content );
|
$content );
|
||||||
|
|
||||||
|
41
bridges/ForGifsBridge.php
Executable file
41
bridges/ForGifsBridge.php
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
class ForGifsBridge extends FeedExpander {
|
||||||
|
|
||||||
|
const MAINTAINER = 'logmanoriginal';
|
||||||
|
const NAME = 'forgifs Bridge';
|
||||||
|
const URI = 'https://forgifs.com';
|
||||||
|
const DESCRIPTION = 'Returns the forgifs feed with actual gifs instead of images';
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
$this->collectExpandableDatas('https://forgifs.com/gallery/srss/7');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseItem($feedItem) {
|
||||||
|
|
||||||
|
$item = parent::parseItem($feedItem);
|
||||||
|
|
||||||
|
$content = str_get_html($item['content']);
|
||||||
|
$img = $content->find('img', 0);
|
||||||
|
$poster = $img->src;
|
||||||
|
|
||||||
|
// The actual gif is the same path but its id must be decremented by one.
|
||||||
|
// Example:
|
||||||
|
// http://forgifs.com/gallery/d/279419-2/Reporter-videobombed-shoulder-checks.gif
|
||||||
|
// http://forgifs.com/gallery/d/279418-2/Reporter-videobombed-shoulder-checks.gif
|
||||||
|
// Notice how this changes ----------^
|
||||||
|
// Now let's extract that number and do some math
|
||||||
|
// Notice: Technically we could also load the content page but that would
|
||||||
|
// require unnecessary traffic. As long as it works...
|
||||||
|
$num = substr($img->src, 29, 6);
|
||||||
|
$num -= 1;
|
||||||
|
$img->src = substr_replace($img->src, $num, 29, strlen($num));
|
||||||
|
$img->width = 'auto';
|
||||||
|
$img->height = 'auto';
|
||||||
|
|
||||||
|
$item['content'] = $content;
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -30,7 +30,7 @@ class FourchanBridge extends BridgeAbstract {
|
|||||||
public function collectData(){
|
public function collectData(){
|
||||||
|
|
||||||
$html = getSimpleHTMLDOM($this->getURI())
|
$html = getSimpleHTMLDOM($this->getURI())
|
||||||
or returnServerError("Could not request 4chan, thread not found");
|
or returnServerError('Could not request 4chan, thread not found');
|
||||||
|
|
||||||
foreach($html->find('div.postContainer') as $element) {
|
foreach($html->find('div.postContainer') as $element) {
|
||||||
$item = array();
|
$item = array();
|
||||||
|
164
bridges/GitHubGistBridge.php
Normal file
164
bridges/GitHubGistBridge.php
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class GitHubGistBridge extends BridgeAbstract {
|
||||||
|
|
||||||
|
const NAME = 'GitHubGist comment bridge';
|
||||||
|
const URI = 'https://gist.github.com';
|
||||||
|
const DESCRIPTION = 'Generates feeds for Gist comments';
|
||||||
|
const MAINTAINER = 'logmanoriginal';
|
||||||
|
const CACHE_TIMEOUT = 3600;
|
||||||
|
|
||||||
|
const PARAMETERS = array(array(
|
||||||
|
'id' => array(
|
||||||
|
'name' => 'Gist',
|
||||||
|
'type' => 'text',
|
||||||
|
'required' => true,
|
||||||
|
'title' => 'Insert Gist ID or URI',
|
||||||
|
'exampleValue' => '2646763, https://gist.github.com/2646763'
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
private $filename;
|
||||||
|
|
||||||
|
public function getURI() {
|
||||||
|
|
||||||
|
$id = $this->getInput('id') ?: '';
|
||||||
|
|
||||||
|
$urlpath = parse_url($id, PHP_URL_PATH);
|
||||||
|
|
||||||
|
if($urlpath) {
|
||||||
|
|
||||||
|
$components = explode('/', $urlpath);
|
||||||
|
$id = end($components);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return static::URI . '/' . $id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName() {
|
||||||
|
return $this->filename ? $this->filename . ' - ' . static::NAME : static::NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
|
||||||
|
$html = getSimpleHTMLDOM($this->getURI(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
DEFAULT_TARGET_CHARSET,
|
||||||
|
false, // Do NOT remove line breaks
|
||||||
|
DEFAULT_BR_TEXT,
|
||||||
|
DEFAULT_SPAN_TEXT)
|
||||||
|
or returnServerError('Could not request ' . $this->getURI());
|
||||||
|
|
||||||
|
$html = defaultLinkTo($html, static::URI);
|
||||||
|
|
||||||
|
$fileinfo = $html->find('[class="file-info"]', 0)
|
||||||
|
or returnServerError('Could not find file info!');
|
||||||
|
|
||||||
|
$this->filename = $fileinfo->plaintext;
|
||||||
|
|
||||||
|
$comments = $html->find('div[class="timeline-comment-wrapper"]');
|
||||||
|
|
||||||
|
if(is_null($comments)) { // no comments yet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($comments as $comment) {
|
||||||
|
|
||||||
|
$uri = $comment->find('a[href^=#gistcomment]', 0)
|
||||||
|
or returnServerError('Could not find comment anchor!');
|
||||||
|
|
||||||
|
$title = $comment->find('div[class="unminimized-comment"] h3[class="timeline-comment-header-text"]', 0)
|
||||||
|
or returnServerError('Could not find comment header text!');
|
||||||
|
|
||||||
|
$datetime = $comment->find('[datetime]', 0)
|
||||||
|
or returnServerError('Could not find comment datetime!');
|
||||||
|
|
||||||
|
$author = $comment->find('a.author', 0)
|
||||||
|
or returnServerError('Could not find author name!');
|
||||||
|
|
||||||
|
$message = $comment->find('[class="comment-body"]', 0)
|
||||||
|
or returnServerError('Could not find comment body!');
|
||||||
|
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
$item['uri'] = $this->getURI() . $uri->href;
|
||||||
|
$item['title'] = str_replace('commented', 'commented on', $title->plaintext);
|
||||||
|
$item['timestamp'] = strtotime($datetime->datetime);
|
||||||
|
$item['author'] = '<a href="' . $author->href . '">' . $author->plaintext . '</a>';
|
||||||
|
$item['content'] = $this->fixContent($message);
|
||||||
|
// $item['enclosures'] = array();
|
||||||
|
// $item['categories'] = array();
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes all unnecessary tags and adds formatting */
|
||||||
|
private function fixContent($content){
|
||||||
|
|
||||||
|
// Restore code (inside <pre />) highlighting
|
||||||
|
foreach($content->find('pre') as $pre) {
|
||||||
|
|
||||||
|
$pre->style = <<<EOD
|
||||||
|
padding: 16px;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 85%;
|
||||||
|
line-height: 1.45;
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
border-radius: 3px;
|
||||||
|
word-wrap: normal;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
EOD;
|
||||||
|
|
||||||
|
$code = $pre->find('code', 0);
|
||||||
|
|
||||||
|
if($code) {
|
||||||
|
|
||||||
|
$code->style = <<<EOD
|
||||||
|
white-space: pre;
|
||||||
|
word-break: normal;
|
||||||
|
EOD;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// find <code /> not inside <pre /> (`inline-code`)
|
||||||
|
foreach($content->find('code') as $code) {
|
||||||
|
|
||||||
|
if($code->parent()->tag === 'pre') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$code->style = <<<EOD
|
||||||
|
background-color: rgba(27,31,35,0.05);
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 3px;
|
||||||
|
EOD;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore text spacing
|
||||||
|
foreach($content->find('p') as $p) {
|
||||||
|
$p->style = 'margin-bottom: 16px;';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove unnecessary tags
|
||||||
|
$content = strip_tags(
|
||||||
|
$content->innertext,
|
||||||
|
'<p><a><img><ol><ul><li><table><tr><th><td><string><pre><code><br><hr><h>'
|
||||||
|
);
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -106,7 +106,7 @@ class GithubIssueBridge extends BridgeAbstract {
|
|||||||
$content = $comment->parent()->innertext;
|
$content = $comment->parent()->innertext;
|
||||||
} else {
|
} else {
|
||||||
$title .= ' / ' . trim($comment->firstChild()->plaintext);
|
$title .= ' / ' . trim($comment->firstChild()->plaintext);
|
||||||
$content = "<pre>" . $comment->find('.comment-body', 0)->innertext . "</pre>";
|
$content = '<pre>' . $comment->find('.comment-body', 0)->innertext . '</pre>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$item = array();
|
$item = array();
|
||||||
|
@@ -3,7 +3,7 @@ class GoComicsBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
const MAINTAINER = 'sky';
|
const MAINTAINER = 'sky';
|
||||||
const NAME = 'GoComics Unofficial RSS';
|
const NAME = 'GoComics Unofficial RSS';
|
||||||
const URI = 'http://www.gocomics.com/';
|
const URI = 'https://www.gocomics.com/';
|
||||||
const CACHE_TIMEOUT = 21600; // 6h
|
const CACHE_TIMEOUT = 21600; // 6h
|
||||||
const DESCRIPTION = 'The Unofficial GoComics RSS';
|
const DESCRIPTION = 'The Unofficial GoComics RSS';
|
||||||
const PARAMETERS = array( array(
|
const PARAMETERS = array( array(
|
||||||
@@ -18,25 +18,27 @@ class GoComicsBridge extends BridgeAbstract {
|
|||||||
$html = getSimpleHTMLDOM($this->getURI())
|
$html = getSimpleHTMLDOM($this->getURI())
|
||||||
or returnServerError('Could not request GoComics: ' . $this->getURI());
|
or returnServerError('Could not request GoComics: ' . $this->getURI());
|
||||||
|
|
||||||
foreach($html->find('div.comic__container') as $element) {
|
//Get info from first page
|
||||||
|
$author = preg_replace('/By /', '', $html->find('.media-subheading', 0)->plaintext);
|
||||||
|
|
||||||
$img = $element->find('.item-comic-image img', 0);
|
$link = self::URI . $html->find('.gc-deck--cta-0', 0)->find('a', 0)->href;
|
||||||
$link = $element->find('a.js-item-comic-link', 0);
|
for($i = 0; $i < 5; $i++) {
|
||||||
$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);
|
|
||||||
|
|
||||||
$item = array();
|
$item = array();
|
||||||
$item['id'] = $url;
|
|
||||||
$item['uri'] = $url;
|
$page = getSimpleHTMLDOM($link)
|
||||||
$item['title'] = $title;
|
or returnServerError('Could not request GoComics: ' . $link);
|
||||||
$item['author'] = preg_replace('/by /', '', $element->find('a.link-blended small', 0)->plaintext);
|
$imagelink = $page->find('.img-fluid', 1)->src;
|
||||||
$item['timestamp'] = $date;
|
$date = explode('/', $link);
|
||||||
$item['content'] = '<img src="' . $comic . '" alt="' . $title . '" />';
|
|
||||||
|
$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;
|
$this->items[] = $item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
class GooglePlusPostBridge extends BridgeAbstract{
|
class GooglePlusPostBridge extends BridgeAbstract{
|
||||||
|
|
||||||
protected $_title;
|
private $title;
|
||||||
protected $_url;
|
private $url;
|
||||||
|
|
||||||
const MAINTAINER = 'Grummfy';
|
const MAINTAINER = 'Grummfy, logmanoriginal';
|
||||||
const NAME = 'Google Plus Post Bridge';
|
const NAME = 'Google Plus Post Bridge';
|
||||||
const URI = 'https://plus.google.com/';
|
const URI = 'https://plus.google.com';
|
||||||
const CACHE_TIMEOUT = 600; //10min
|
const CACHE_TIMEOUT = 600; //10min
|
||||||
const DESCRIPTION = 'Returns user public post (without API).';
|
const DESCRIPTION = 'Returns user public post (without API).';
|
||||||
|
|
||||||
@@ -14,10 +14,16 @@ class GooglePlusPostBridge extends BridgeAbstract{
|
|||||||
'username' => array(
|
'username' => array(
|
||||||
'name' => 'username or Id',
|
'name' => 'username or Id',
|
||||||
'required' => true
|
'required' => true
|
||||||
|
),
|
||||||
|
'include_media' => array(
|
||||||
|
'name' => 'Include media',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'title' => 'Enable to include media in the feed content'
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
|
|
||||||
$username = $this->getInput('username');
|
$username = $this->getInput('username');
|
||||||
|
|
||||||
// Usernames start with a + if it's not an ID
|
// Usernames start with a + if it's not an ID
|
||||||
@@ -25,22 +31,20 @@ class GooglePlusPostBridge extends BridgeAbstract{
|
|||||||
$username = '+' . $username;
|
$username = '+' . $username;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get content parsed
|
$html = getSimpleHTMLDOM(static::URI . '/' . urlencode($username) . '/posts')
|
||||||
$html = getSimpleHTMLDOMCached(self::URI . urlencode($username) . '/posts')
|
|
||||||
or returnServerError('No results for this query.');
|
or returnServerError('No results for this query.');
|
||||||
|
|
||||||
// get title, url, ... there is a lot of intresting stuff in meta
|
$html = defaultLinkTo($html, static::URI);
|
||||||
$this->_title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
|
|
||||||
$this->_url = $html->find('meta[property=og:url]', 0)->getAttribute('content');
|
$this->title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
|
||||||
|
$this->url = $html->find('meta[property=og:url]', 0)->getAttribute('content');
|
||||||
|
|
||||||
// I don't even know where to start with this discusting html...
|
|
||||||
foreach($html->find('div[jsname=WsjYwc]') as $post) {
|
foreach($html->find('div[jsname=WsjYwc]') as $post) {
|
||||||
|
|
||||||
$item = array();
|
$item = array();
|
||||||
|
|
||||||
$item['author'] = $item['fullname'] = $post->find('div div div div a', 0)->innertext;
|
$item['author'] = $post->find('div div div div a', 0)->innertext;
|
||||||
$item['id'] = $post->find('div div div', 0)->getAttribute('id');
|
$item['uri'] = $post->find('div div div a', 1)->href;
|
||||||
$item['avatar'] = $post->find('div img', 0)->src;
|
|
||||||
$item['uri'] = self::URI . $post->find('div div div a', 1)->href;
|
|
||||||
|
|
||||||
$timestamp = $post->find('a.qXj2He span', 0);
|
$timestamp = $post->find('a.qXj2He span', 0);
|
||||||
|
|
||||||
@@ -51,61 +55,149 @@ class GooglePlusPostBridge extends BridgeAbstract{
|
|||||||
$timestamp->getAttribute('aria-label')));
|
$timestamp->getAttribute('aria-label')));
|
||||||
}
|
}
|
||||||
|
|
||||||
// hashtag to treat : https://plus.google.com/explore/tag
|
$message = $post->find('div[jsname=EjRJtf]', 0);
|
||||||
// $hashtags = array();
|
|
||||||
// foreach($post->find('a.d-s') as $hashtag){
|
|
||||||
// $hashtags[trim($hashtag->plaintext)] = self::URI . $hashtag->href;
|
|
||||||
// }
|
|
||||||
|
|
||||||
$item['content'] = '';
|
// Empty messages are not supported right now
|
||||||
|
if(!$message) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// avatar display
|
$item['content'] = '<div style="float: left; padding: 0 10px 10px 0;"><a href="'
|
||||||
$item['content'] .= '<div style="float:left; margin: 0 0.5em 0.5em 0;"><a href="'
|
. $this->url
|
||||||
. self::URI
|
. '"><img align="top" alt="'
|
||||||
. urlencode($this->getInput('username'));
|
|
||||||
|
|
||||||
$item['content'] .= '"><img align="top" alt="'
|
|
||||||
. $item['author']
|
. $item['author']
|
||||||
. '" src="'
|
. '" src="'
|
||||||
. $item['avatar']
|
. $post->find('div img', 0)->src
|
||||||
. '" /></a></div>';
|
. '" /></a></div><div>'
|
||||||
|
. trim(strip_tags($message, '<a><p><div><img>'))
|
||||||
|
. '</div>';
|
||||||
|
|
||||||
$content = $post->find('div[jsname=EjRJtf]', 0);
|
// Make title at least 50 characters long, but don't add '...' if it is shorter!
|
||||||
// extract plaintext
|
if(strlen($message->plaintext) > 50) {
|
||||||
$item['content_simple'] = $content->plaintext;
|
$end = strpos($message->plaintext, ' ', 50);
|
||||||
$item['title'] = substr($item['content_simple'], 0, 72) . '...';
|
|
||||||
|
|
||||||
// XXX ugly but I don't have any idea how to do a better stuff,
|
|
||||||
// str_replace on link doesn't work as expected and ask too many checks
|
|
||||||
foreach($content->find('a') as $link) {
|
|
||||||
$hasHttp = strpos($link->href, 'http');
|
|
||||||
$hasDoubleSlash = strpos($link->href, '//');
|
|
||||||
|
|
||||||
if((!$hasHttp && !$hasDoubleSlash)
|
|
||||||
|| (false !== $hasHttp && strpos($link->href, 'http') != 0)
|
|
||||||
|| (false === $hasHttp && false !== $hasDoubleSlash && $hasDoubleSlash != 0)) {
|
|
||||||
// skipp bad link, for some hashtag or other stuff
|
|
||||||
if(strpos($link->href, '/') == 0) {
|
|
||||||
$link->href = substr($link->href, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$link->href = self::URI . $link->href;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$content = $content->innertext;
|
|
||||||
|
|
||||||
$item['content'] .= '<div style="margin-top: -1.5em">' . $content . '</div>';
|
if(strlen(substr($message->plaintext, 0, $end)) === strlen($message->plaintext)) {
|
||||||
$item['content'] = trim(strip_tags($item['content'], '<a><p><div><img>'));
|
$item['title'] = $message->plaintext;
|
||||||
|
} else {
|
||||||
|
$item['title'] = substr($message->plaintext, 0, $end) . '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
$media = $post->find('[jsname="MTOxpb"]', 0);
|
||||||
|
|
||||||
|
if($media) {
|
||||||
|
|
||||||
|
$item['enclosures'] = array();
|
||||||
|
|
||||||
|
foreach($media->find('img') as $img) {
|
||||||
|
$item['enclosures'][] = $this->fixImage($img)->src;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->getInput('include_media') === true && count($item['enclosures'] > 0)) {
|
||||||
|
$item['content'] .= '<div style="clear: both;"><a href="'
|
||||||
|
. $item['enclosures'][0]
|
||||||
|
. '"><img src="'
|
||||||
|
. $item['enclosures'][0]
|
||||||
|
. '" /></a></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom parameters (only useful for JSON or Plaintext)
|
||||||
|
$item['fullname'] = $item['author'];
|
||||||
|
$item['avatar'] = $post->find('div img', 0)->src;
|
||||||
|
$item['id'] = $post->find('div div div', 0)->getAttribute('id');
|
||||||
|
$item['content_simple'] = $message->plaintext;
|
||||||
|
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName(){
|
public function getName(){
|
||||||
return $this->_title ?: 'Google Plus Post Bridge';
|
return $this->title ?: 'Google Plus Post Bridge';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getURI(){
|
public function getURI(){
|
||||||
return $this->_url ?: parent::getURI();
|
return $this->url ?: parent::getURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function fixImage($img) {
|
||||||
|
|
||||||
|
// There are certain images like .gif which link to a static picture and
|
||||||
|
// get replaced dynamically via JS in the browser. If we want the "real"
|
||||||
|
// image we need to account for that.
|
||||||
|
|
||||||
|
$urlparts = parse_url($img->src);
|
||||||
|
|
||||||
|
if(array_key_exists('host', $urlparts)) {
|
||||||
|
|
||||||
|
// For some reason some URIs don't contain the scheme, assume https
|
||||||
|
if(!array_key_exists('scheme', $urlparts)) {
|
||||||
|
$urlparts['scheme'] = 'https';
|
||||||
|
}
|
||||||
|
|
||||||
|
$pathelements = explode('/', $urlparts['path']);
|
||||||
|
|
||||||
|
switch($urlparts['host']) {
|
||||||
|
|
||||||
|
case 'lh3.googleusercontent.com':
|
||||||
|
|
||||||
|
if(pathinfo(end($pathelements), PATHINFO_EXTENSION)) {
|
||||||
|
|
||||||
|
// The second to last element of the path specifies the
|
||||||
|
// image format. The URL is still valid if we remove it.
|
||||||
|
unset($pathelements[count($pathelements) - 2]);
|
||||||
|
|
||||||
|
} elseif(strrpos(end($pathelements), '=') !== false) {
|
||||||
|
|
||||||
|
// Some images go throug a proxy. For those images they
|
||||||
|
// add size information after an equal sign.
|
||||||
|
// Example: '=w530-h298-n'. Again this can safely be
|
||||||
|
// removed to get the original image.
|
||||||
|
$pathelements[count($pathelements) - 1] = substr(
|
||||||
|
end($pathelements),
|
||||||
|
0,
|
||||||
|
strrpos(end($pathelements), '=')
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$urlparts['path'] = implode('/', $pathelements);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$img->src = $this->build_url($urlparts);
|
||||||
|
return $img;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From: https://gist.github.com/Ellrion/f51ba0d40ae1d62eeae44fd1adf7b704
|
||||||
|
* slightly adjusted to work with PHP < 7.0
|
||||||
|
* @param array $parts
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function build_url(array $parts)
|
||||||
|
{
|
||||||
|
|
||||||
|
$scheme = isset($parts['scheme']) ? ($parts['scheme'] . '://') : '';
|
||||||
|
$host = isset($parts['host']) ? $parts['host'] : '';
|
||||||
|
$port = isset($parts['port']) ? (':' . $parts['port']) : '';
|
||||||
|
$user = isset($parts['user']) ? $parts['user'] : '';
|
||||||
|
$pass = isset($parts['pass']) ? (':' . $parts['pass']) : '';
|
||||||
|
$pass = ($user || $pass) ? ($pass . '@') : '';
|
||||||
|
$path = isset($parts['path']) ? $parts['path'] : '';
|
||||||
|
$query = isset($parts['query']) ? ('?' . $parts['query']) : '';
|
||||||
|
$fragment = isset($parts['fragment']) ? ('#' . $parts['fragment']) : '';
|
||||||
|
|
||||||
|
return implode('', [$scheme, $user, $pass, $host, $port, $path, $query, $fragment]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,7 @@ class GoogleSearchBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
const PARAMETERS = array(array(
|
const PARAMETERS = array(array(
|
||||||
'q' => array(
|
'q' => array(
|
||||||
'name' => "keyword",
|
'name' => 'keyword',
|
||||||
'required' => true
|
'required' => true
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
61
bridges/GrandComicsDatabaseBridge.php
Normal file
61
bridges/GrandComicsDatabaseBridge.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
class GrandComicsDatabaseBridge extends BridgeAbstract {
|
||||||
|
|
||||||
|
const MAINTAINER = 'corenting';
|
||||||
|
const NAME = 'Grand Comics Database Bridge';
|
||||||
|
const URI = 'https://www.comics.org/';
|
||||||
|
const CACHE_TIMEOUT = 7200; // 2h
|
||||||
|
const DESCRIPTION = 'Returns the latest comics added to a series timeline';
|
||||||
|
const PARAMETERS = array( array(
|
||||||
|
'series' => array(
|
||||||
|
'name' => 'Series id (from the timeline URL)',
|
||||||
|
'required' => true,
|
||||||
|
'exampleValue' => '63051',
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
public function collectData(){
|
||||||
|
|
||||||
|
$url = self::URI . 'series/' . $this->getInput('series') . '/details/timeline/';
|
||||||
|
$html = getSimpleHTMLDOM($url)
|
||||||
|
or returnServerError('Error while downloading the website content');
|
||||||
|
|
||||||
|
$table = $html->find('table', 0);
|
||||||
|
$list = array_reverse($table->find('[class^=row_even]'));
|
||||||
|
$seriesName = $html->find('span[id=series_name]', 0)->innertext;
|
||||||
|
|
||||||
|
// Get row headers
|
||||||
|
$rowHeaders = $table->find('th');
|
||||||
|
foreach($list as $article) {
|
||||||
|
|
||||||
|
// Skip empty rows
|
||||||
|
$emptyRow = $article->find('td.empty_month');
|
||||||
|
if (count($emptyRow) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $article->find('td');
|
||||||
|
$key_date = $rows[0]->innertext;
|
||||||
|
|
||||||
|
// Get URL too
|
||||||
|
$uri = 'https://www.comics.org' . $article->find('a')[0]->href;
|
||||||
|
|
||||||
|
// Build content
|
||||||
|
$content = '';
|
||||||
|
for($i = 0; $i < count($rowHeaders); $i++) {
|
||||||
|
$headerItem = $rowHeaders[$i]->innertext;
|
||||||
|
$rowItem = $rows[$i]->innertext;
|
||||||
|
$content = $content . $headerItem . ': ' . $rowItem . '<br/>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build final item
|
||||||
|
$item = array();
|
||||||
|
$item['title'] = $seriesName . ' - ' . $key_date;
|
||||||
|
$item['timestamp'] = strtotime($key_date);
|
||||||
|
$item['content'] = str_get_html($content);
|
||||||
|
$item['uri'] = $uri;
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1397
bridges/HotUKDealsBridge.php
Normal file
1397
bridges/HotUKDealsBridge.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,60 +11,38 @@ class InstagramBridge extends BridgeAbstract {
|
|||||||
'u' => array(
|
'u' => array(
|
||||||
'name' => 'username',
|
'name' => 'username',
|
||||||
'required' => true
|
'required' => true
|
||||||
),
|
|
||||||
'media_type' => array(
|
|
||||||
'name' => 'Media type',
|
|
||||||
'type' => 'list',
|
|
||||||
'required' => false,
|
|
||||||
'values' => array(
|
|
||||||
'Both' => 'all',
|
|
||||||
'Video' => 'video',
|
|
||||||
'Picture' => 'picture'
|
|
||||||
),
|
|
||||||
'defaultValue' => 'all'
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'h' => array(
|
'h' => array(
|
||||||
'name' => 'hashtag',
|
'name' => 'hashtag',
|
||||||
'required' => true
|
'required' => true
|
||||||
),
|
)
|
||||||
|
),
|
||||||
|
'global' => array(
|
||||||
'media_type' => array(
|
'media_type' => array(
|
||||||
'name' => 'Media type',
|
'name' => 'Media type',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'values' => array(
|
'values' => array(
|
||||||
'Both' => 'all',
|
'All' => 'all',
|
||||||
|
'Story' => 'story',
|
||||||
'Video' => 'video',
|
'Video' => 'video',
|
||||||
'Picture' => 'picture'
|
'Picture' => 'picture',
|
||||||
),
|
),
|
||||||
'defaultValue' => 'all'
|
'defaultValue' => 'all'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
$html = getSimpleHTMLDOM($this->getURI())
|
|
||||||
or returnServerError('Could not request Instagram.');
|
|
||||||
|
|
||||||
$innertext = null;
|
if(!is_null($this->getInput('h')) && $this->getInput('media_type') == 'story') {
|
||||||
|
returnClientError('Stories are not supported for hashtags!');
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$json = trim(substr($innertext, $pos + 18), ' =;');
|
$data = $this->getInstagramJSON($this->getURI());
|
||||||
$data = json_decode($json);
|
|
||||||
|
|
||||||
if(!is_null($this->getInput('u'))) {
|
if(!is_null($this->getInput('u'))) {
|
||||||
$userMedia = $data->entry_data->ProfilePage[0]->graphql->user->edge_owner_to_timeline_media->edges;
|
$userMedia = $data->entry_data->ProfilePage[0]->graphql->user->edge_owner_to_timeline_media->edges;
|
||||||
@@ -74,31 +52,83 @@ class InstagramBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
foreach($userMedia as $media) {
|
foreach($userMedia as $media) {
|
||||||
$media = $media->node;
|
$media = $media->node;
|
||||||
// Check media type
|
|
||||||
switch($this->getInput('media_type')) {
|
if(!is_null($this->getInput('u'))) {
|
||||||
case 'all': break;
|
switch($this->getInput('media_type')) {
|
||||||
case 'video':
|
case 'all': break;
|
||||||
if($media->is_video === false) continue 2;
|
case 'video':
|
||||||
break;
|
if($media->__typename != 'GraphVideo') continue 2;
|
||||||
case 'picture':
|
break;
|
||||||
if($media->is_video === true) continue 2;
|
case 'picture':
|
||||||
break;
|
if($media->__typename != 'GraphImage') continue 2;
|
||||||
default: break;
|
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 = array();
|
||||||
$item['uri'] = self::URI . 'p/' . $media->shortcode . '/';
|
$item['uri'] = self::URI . 'p/' . $media->shortcode . '/';
|
||||||
$item['content'] = '<img src="' . htmlentities($media->display_url) . '" />';
|
|
||||||
if (isset($media->edge_media_to_caption->edges[0]->node->text)) {
|
if (isset($media->edge_media_to_caption->edges[0]->node->text)) {
|
||||||
$item['title'] = $media->edge_media_to_caption->edges[0]->node->text;
|
$item['title'] = $media->edge_media_to_caption->edges[0]->node->text;
|
||||||
} else {
|
} else {
|
||||||
$item['title'] = basename($media->display_url);
|
$item['title'] = basename($media->display_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
$item['timestamp'] = $media->taken_at_timestamp;
|
||||||
|
|
||||||
$this->items[] = $item;
|
$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(){
|
public function getName(){
|
||||||
if(!is_null($this->getInput('u'))) {
|
if(!is_null($this->getInput('u'))) {
|
||||||
return $this->getInput('u') . ' - Instagram Bridge';
|
return $this->getInput('u') . ' - Instagram Bridge';
|
||||||
|
370
bridges/InstructablesBridge.php
Normal file
370
bridges/InstructablesBridge.php
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This class implements a bridge for http://www.instructables.com, supporting
|
||||||
|
* general feeds and feeds by category. Instructables doesn't support HTTPS as
|
||||||
|
* of now (23.06.2018), so all connections are insecure!
|
||||||
|
*
|
||||||
|
* Remarks:
|
||||||
|
* - For some reason it is very important to have the category URI end with a
|
||||||
|
* slash, otherwise the site defaults to the main category (i.e. Technology)!
|
||||||
|
* If you need to update the categories list, enable the 'listCategories'
|
||||||
|
* function (see comments below) and run the bridge with format=Html (see page
|
||||||
|
* source)
|
||||||
|
*/
|
||||||
|
class InstructablesBridge extends BridgeAbstract {
|
||||||
|
const NAME = 'Instructables Bridge';
|
||||||
|
const URI = 'http://www.instructables.com';
|
||||||
|
const DESCRIPTION = 'Returns general feeds and feeds by category';
|
||||||
|
const MAINTAINER = 'logmanoriginal';
|
||||||
|
const PARAMETERS = array(
|
||||||
|
'Category' => array(
|
||||||
|
'category' => array(
|
||||||
|
'name' => 'Category',
|
||||||
|
'type' => 'list',
|
||||||
|
'required' => true,
|
||||||
|
'values' => array(
|
||||||
|
'Play' => array(
|
||||||
|
'All' => '/play/',
|
||||||
|
'KNEX' => '/play/knex/',
|
||||||
|
'Offbeat' => '/play/offbeat/',
|
||||||
|
'Lego' => '/play/lego/',
|
||||||
|
'Airsoft' => '/play/airsoft/',
|
||||||
|
'Card Games' => '/play/card-games/',
|
||||||
|
'Guitars' => '/play/guitars/',
|
||||||
|
'Instruments' => '/play/instruments/',
|
||||||
|
'Magic Tricks' => '/play/magic-tricks/',
|
||||||
|
'Minecraft' => '/play/minecraft/',
|
||||||
|
'Music' => '/play/music/',
|
||||||
|
'Nerf' => '/play/nerf/',
|
||||||
|
'Nintendo' => '/play/nintendo/',
|
||||||
|
'Office Supplies' => '/play/office-supplies/',
|
||||||
|
'Paintball' => '/play/paintball/',
|
||||||
|
'Paper Airplanes' => '/play/paper-airplanes/',
|
||||||
|
'Party Tricks' => '/play/party-tricks/',
|
||||||
|
'PlayStation' => '/play/playstation/',
|
||||||
|
'Pranks and Humor' => '/play/pranks-and-humor/',
|
||||||
|
'Puzzles' => '/play/puzzles/',
|
||||||
|
'Siege Engines' => '/play/siege-engines/',
|
||||||
|
'Sports' => '/play/sports/',
|
||||||
|
'Table Top' => '/play/table-top/',
|
||||||
|
'Toys' => '/play/toys/',
|
||||||
|
'Video Games' => '/play/video-games/',
|
||||||
|
'Wii' => '/play/wii/',
|
||||||
|
'Xbox' => '/play/xbox/',
|
||||||
|
'Yo-Yo' => '/play/yo-yo/',
|
||||||
|
),
|
||||||
|
'Craft' => array(
|
||||||
|
'All' => '/craft/',
|
||||||
|
'Art' => '/craft/art/',
|
||||||
|
'Sewing' => '/craft/sewing/',
|
||||||
|
'Paper' => '/craft/paper/',
|
||||||
|
'Jewelry' => '/craft/jewelry/',
|
||||||
|
'Fashion' => '/craft/fashion/',
|
||||||
|
'Books & Journals' => '/craft/books-and-journals/',
|
||||||
|
'Cards' => '/craft/cards/',
|
||||||
|
'Clay' => '/craft/clay/',
|
||||||
|
'Duct Tape' => '/craft/duct-tape/',
|
||||||
|
'Embroidery' => '/craft/embroidery/',
|
||||||
|
'Felt' => '/craft/felt/',
|
||||||
|
'Fiber Arts' => '/craft/fiber-arts/',
|
||||||
|
'Gifts & Wrapping' => '/craft/gifts-and-wrapping/',
|
||||||
|
'Knitting & Crocheting' => '/craft/knitting-and-crocheting/',
|
||||||
|
'Leather' => '/craft/leather/',
|
||||||
|
'Mason Jars' => '/craft/mason-jars/',
|
||||||
|
'No-Sew' => '/craft/no-sew/',
|
||||||
|
'Parties & Weddings' => '/craft/parties-and-weddings/',
|
||||||
|
'Print Making' => '/craft/print-making/',
|
||||||
|
'Soap' => '/craft/soap/',
|
||||||
|
'Wallets' => '/craft/wallets/',
|
||||||
|
),
|
||||||
|
'Technology' => array(
|
||||||
|
'All' => '/technology/',
|
||||||
|
'Electronics' => '/technology/electronics/',
|
||||||
|
'Arduino' => '/technology/arduino/',
|
||||||
|
'Photography' => '/technology/photography/',
|
||||||
|
'Leds' => '/technology/leds/',
|
||||||
|
'Science' => '/technology/science/',
|
||||||
|
'Reuse' => '/technology/reuse/',
|
||||||
|
'Apple' => '/technology/apple/',
|
||||||
|
'Computers' => '/technology/computers/',
|
||||||
|
'3D Printing' => '/technology/3D-Printing/',
|
||||||
|
'Robots' => '/technology/robots/',
|
||||||
|
'Art' => '/technology/art/',
|
||||||
|
'Assistive Tech' => '/technology/assistive-technology/',
|
||||||
|
'Audio' => '/technology/audio/',
|
||||||
|
'Clocks' => '/technology/clocks/',
|
||||||
|
'CNC' => '/technology/cnc/',
|
||||||
|
'Digital Graphics' => '/technology/digital-graphics/',
|
||||||
|
'Gadgets' => '/technology/gadgets/',
|
||||||
|
'Kits' => '/technology/kits/',
|
||||||
|
'Laptops' => '/technology/laptops/',
|
||||||
|
'Lasers' => '/technology/lasers/',
|
||||||
|
'Linux' => '/technology/linux/',
|
||||||
|
'Microcontrollers' => '/technology/microcontrollers/',
|
||||||
|
'Microsoft' => '/technology/microsoft/',
|
||||||
|
'Mobile' => '/technology/mobile/',
|
||||||
|
'Raspberry Pi' => '/technology/raspberry-pi/',
|
||||||
|
'Remote Control' => '/technology/remote-control/',
|
||||||
|
'Sensors' => '/technology/sensors/',
|
||||||
|
'Software' => '/technology/software/',
|
||||||
|
'Soldering' => '/technology/soldering/',
|
||||||
|
'Speakers' => '/technology/speakers/',
|
||||||
|
'Steampunk' => '/technology/steampunk/',
|
||||||
|
'Tools' => '/technology/tools/',
|
||||||
|
'USB' => '/technology/usb/',
|
||||||
|
'Wearables' => '/technology/wearables/',
|
||||||
|
'Websites' => '/technology/websites/',
|
||||||
|
'Wireless' => '/technology/wireless/',
|
||||||
|
),
|
||||||
|
'Workshop' => array(
|
||||||
|
'All' => '/workshop/',
|
||||||
|
'Woodworking' => '/workshop/woodworking/',
|
||||||
|
'Tools' => '/workshop/tools/',
|
||||||
|
'Gardening' => '/workshop/gardening/',
|
||||||
|
'Cars' => '/workshop/cars/',
|
||||||
|
'Metalworking' => '/workshop/metalworking/',
|
||||||
|
'Cardboard' => '/workshop/cardboard/',
|
||||||
|
'Electric Vehicles' => '/workshop/electric-vehicles/',
|
||||||
|
'Energy' => '/workshop/energy/',
|
||||||
|
'Furniture' => '/workshop/furniture/',
|
||||||
|
'Home Improvement' => '/workshop/home-improvement/',
|
||||||
|
'Home Theater' => '/workshop/home-theater/',
|
||||||
|
'Hydroponics' => '/workshop/hydroponics/',
|
||||||
|
'Laser Cutting' => '/workshop/laser-cutting/',
|
||||||
|
'Lighting' => '/workshop/lighting/',
|
||||||
|
'Molds & Casting' => '/workshop/molds-and-casting/',
|
||||||
|
'Motorcycles' => '/workshop/motorcycles/',
|
||||||
|
'Organizing' => '/workshop/organizing/',
|
||||||
|
'Pallets' => '/workshop/pallets/',
|
||||||
|
'Repair' => '/workshop/repair/',
|
||||||
|
'Shelves' => '/workshop/shelves/',
|
||||||
|
'Solar' => '/workshop/solar/',
|
||||||
|
'Workbenches' => '/workshop/workbenches/',
|
||||||
|
),
|
||||||
|
'Home' => array(
|
||||||
|
'All' => '/home/',
|
||||||
|
'Halloween' => '/home/halloween/',
|
||||||
|
'Decorating' => '/home/decorating/',
|
||||||
|
'Organizing' => '/home/organizing/',
|
||||||
|
'Pets' => '/home/pets/',
|
||||||
|
'Life Hacks' => '/home/life-hacks/',
|
||||||
|
'Beauty' => '/home/beauty/',
|
||||||
|
'Christmas' => '/home/christmas/',
|
||||||
|
'Cleaning' => '/home/cleaning/',
|
||||||
|
'Education' => '/home/education/',
|
||||||
|
'Finances' => '/home/finances/',
|
||||||
|
'Gardening' => '/home/gardening/',
|
||||||
|
'Green' => '/home/green/',
|
||||||
|
'Health' => '/home/health/',
|
||||||
|
'Hiding Places' => '/home/hiding-places/',
|
||||||
|
'Holidays' => '/home/holidays/',
|
||||||
|
'Homesteading' => '/home/homesteading/',
|
||||||
|
'Kids' => '/home/kids/',
|
||||||
|
'Kitchen' => '/home/kitchen/',
|
||||||
|
'Life Skills' => '/home/life-skills/',
|
||||||
|
'Parenting' => '/home/parenting/',
|
||||||
|
'Pest Control' => '/home/pest-control/',
|
||||||
|
'Relationships' => '/home/relationships/',
|
||||||
|
'Reuse' => '/home/reuse/',
|
||||||
|
'Travel' => '/home/travel/',
|
||||||
|
),
|
||||||
|
'Outside' => array(
|
||||||
|
'All' => '/outside/',
|
||||||
|
'Bikes' => '/outside/bikes/',
|
||||||
|
'Survival' => '/outside/survival/',
|
||||||
|
'Backyard' => '/outside/backyard/',
|
||||||
|
'Beach' => '/outside/beach/',
|
||||||
|
'Birding' => '/outside/birding/',
|
||||||
|
'Boats' => '/outside/boats/',
|
||||||
|
'Camping' => '/outside/camping/',
|
||||||
|
'Climbing' => '/outside/climbing/',
|
||||||
|
'Fire' => '/outside/fire/',
|
||||||
|
'Fishing' => '/outside/fishing/',
|
||||||
|
'Hunting' => '/outside/hunting/',
|
||||||
|
'Kites' => '/outside/kites/',
|
||||||
|
'Knives' => '/outside/knives/',
|
||||||
|
'Knots' => '/outside/knots/',
|
||||||
|
'Paracord' => '/outside/paracord/',
|
||||||
|
'Rockets' => '/outside/rockets/',
|
||||||
|
'Skateboarding' => '/outside/skateboarding/',
|
||||||
|
'Snow' => '/outside/snow/',
|
||||||
|
'Water' => '/outside/water/',
|
||||||
|
),
|
||||||
|
'Food' => array(
|
||||||
|
'All' => '/food/',
|
||||||
|
'Dessert' => '/food/dessert/',
|
||||||
|
'Snacks & Appetizers' => '/food/snacks-and-appetizers/',
|
||||||
|
'Bacon' => '/food/bacon/',
|
||||||
|
'BBQ & Grilling' => '/food/bbq-and-grilling/',
|
||||||
|
'Beverages' => '/food/beverages/',
|
||||||
|
'Bread' => '/food/bread/',
|
||||||
|
'Breakfast' => '/food/breakfast/',
|
||||||
|
'Cake' => '/food/cake/',
|
||||||
|
'Candy' => '/food/candy/',
|
||||||
|
'Canning & Preserves' => '/food/canning-and-preserves/',
|
||||||
|
'Cocktails & Mocktails' => '/food/cocktails-and-mocktails/',
|
||||||
|
'Coffee' => '/food/coffee/',
|
||||||
|
'Cookies' => '/food/cookies/',
|
||||||
|
'Cupcakes' => '/food/cupcakes/',
|
||||||
|
'Homebrew' => '/food/homebrew/',
|
||||||
|
'Main Course' => '/food/main-course/',
|
||||||
|
'Pasta' => '/food/pasta/',
|
||||||
|
'Pie' => '/food/pie/',
|
||||||
|
'Pizza' => '/food/pizza/',
|
||||||
|
'Salad' => '/food/salad/',
|
||||||
|
'Sandwiches' => '/food/sandwiches/',
|
||||||
|
'Soups & Stews' => '/food/soups-and-stews/',
|
||||||
|
'Vegetarian & Vegan' => '/food/vegetarian-and-vegan/',
|
||||||
|
),
|
||||||
|
'Costumes' => array(
|
||||||
|
'All' => '/costumes/',
|
||||||
|
'Props' => '/costumes/props-and-accessories/',
|
||||||
|
'Animals' => '/costumes/animals/',
|
||||||
|
'Comics' => '/costumes/comics/',
|
||||||
|
'Fantasy' => '/costumes/fantasy/',
|
||||||
|
'For Kids' => '/costumes/for-kids/',
|
||||||
|
'For Pets' => '/costumes/for-pets/',
|
||||||
|
'Funny' => '/costumes/funny/',
|
||||||
|
'Games' => '/costumes/games/',
|
||||||
|
'Historic & Futuristic' => '/costumes/historic-and-futuristic/',
|
||||||
|
'Makeup' => '/costumes/makeup/',
|
||||||
|
'Masks' => '/costumes/masks/',
|
||||||
|
'Scary' => '/costumes/scary/',
|
||||||
|
'TV & Movies' => '/costumes/tv-and-movies/',
|
||||||
|
'Weapons & Armor' => '/costumes/weapons-and-armor/',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'title' => 'Select your category (required)',
|
||||||
|
'defaultValue' => 'Technology'
|
||||||
|
),
|
||||||
|
'filter' => array(
|
||||||
|
'name' => 'Filter',
|
||||||
|
'type' => 'list',
|
||||||
|
'required' => true,
|
||||||
|
'values' => array(
|
||||||
|
'Featured' => ' ',
|
||||||
|
'Recent' => 'recent/',
|
||||||
|
'Popular' => 'popular/',
|
||||||
|
'Views' => 'views/',
|
||||||
|
'Contest Winners' => 'winners/'
|
||||||
|
),
|
||||||
|
'title' => 'Select a filter',
|
||||||
|
'defaultValue' => 'Featured'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
private $uri;
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
// Enable the following line to get the category list (dev mode)
|
||||||
|
// $this->listCategories();
|
||||||
|
|
||||||
|
$this->uri = static::URI;
|
||||||
|
|
||||||
|
switch($this->queriedContext) {
|
||||||
|
case 'Category': $this->uri .= $this->getInput('category') . $this->getInput('filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = getSimpleHTMLDOM($this->uri)
|
||||||
|
or returnServerError('Error loading category ' . $this->uri);
|
||||||
|
|
||||||
|
foreach($html->find('ul.explore-covers-list li') as $cover) {
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
$item['uri'] = static::URI . $cover->find('a.cover-image', 0)->href;
|
||||||
|
$item['title'] = $cover->find('.title', 0)->innertext;
|
||||||
|
$item['author'] = $this->getCategoryAuthor($cover);
|
||||||
|
$item['content'] = '<a href='
|
||||||
|
. $item['uri']
|
||||||
|
. '><img src='
|
||||||
|
. $cover->find('a.cover-image img', 0)->src
|
||||||
|
. '></a>';
|
||||||
|
|
||||||
|
$image = str_replace('.RECTANGLE1', '.LARGE', $cover->find('a.cover-image img', 0)->src);
|
||||||
|
$item['enclosures'] = [$image];
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName() {
|
||||||
|
if(!is_null($this->getInput('category'))
|
||||||
|
&& !is_null($this->getInput('filter'))) {
|
||||||
|
foreach(self::PARAMETERS[$this->queriedContext]['category']['values'] as $key => $value) {
|
||||||
|
$subcategory = array_search($this->getInput('category'), $value);
|
||||||
|
|
||||||
|
if($subcategory !== false)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filter = array_search(
|
||||||
|
$this->getInput('filter'),
|
||||||
|
self::PARAMETERS[$this->queriedContext]['filter']['values']
|
||||||
|
);
|
||||||
|
|
||||||
|
return $subcategory . ' (' . $filter . ') - ' . static::NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getURI() {
|
||||||
|
if(!is_null($this->getInput('category'))
|
||||||
|
&& !is_null($this->getInput('filter'))) {
|
||||||
|
return $this->uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of categories for development purposes (used to build the
|
||||||
|
* parameters list)
|
||||||
|
*/
|
||||||
|
private function listCategories(){
|
||||||
|
// Use arbitrary category to receive full list
|
||||||
|
$html = getSimpleHTMLDOM(self::URI . '/technology/');
|
||||||
|
|
||||||
|
foreach($html->find('.channel a') as $channel) {
|
||||||
|
$name = html_entity_decode(trim($channel->innertext));
|
||||||
|
|
||||||
|
// Remove unwanted entities
|
||||||
|
$name = str_replace("'", '', $name);
|
||||||
|
$name = str_replace(''', '', $name);
|
||||||
|
|
||||||
|
$uri = $channel->href;
|
||||||
|
|
||||||
|
$category = explode('/', $uri)[1];
|
||||||
|
|
||||||
|
if(!isset($categories)
|
||||||
|
|| !array_key_exists($category, $categories)
|
||||||
|
|| !in_array($uri, $categories[$category]))
|
||||||
|
$categories[$category][$name] = $uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build PHP array manually
|
||||||
|
foreach($categories as $key => $value) {
|
||||||
|
$name = ucfirst($key);
|
||||||
|
echo "'{$name}' => array(\n";
|
||||||
|
echo "\t'All' => '/{$key}/',\n";
|
||||||
|
foreach($value as $name => $uri) {
|
||||||
|
echo "\t'{$name}' => '{$uri}',\n";
|
||||||
|
}
|
||||||
|
echo "),\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
die;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the author as anchor for a given cover.
|
||||||
|
*/
|
||||||
|
private function getCategoryAuthor($cover) {
|
||||||
|
return '<a href='
|
||||||
|
. static::URI . $cover->find('span.author a', 0)->href
|
||||||
|
. '>'
|
||||||
|
. $cover->find('span.author a', 0)->innertext
|
||||||
|
. '</a>';
|
||||||
|
}
|
||||||
|
}
|
@@ -1,465 +0,0 @@
|
|||||||
<?php
|
|
||||||
class IsoHuntBridge extends BridgeAbstract {
|
|
||||||
const MAINTAINER = 'logmanoriginal';
|
|
||||||
const NAME = 'isoHunt Bridge';
|
|
||||||
const URI = 'https://isohunt.to/';
|
|
||||||
const CACHE_TIMEOUT = 300; //5min
|
|
||||||
const DESCRIPTION = 'Returns the latest results by category or search result';
|
|
||||||
|
|
||||||
const PARAMETERS = array(
|
|
||||||
/*
|
|
||||||
* Get feeds for one of the "latest" categories
|
|
||||||
* Notice: The categories "News" and "Top Searches" are received from the main page
|
|
||||||
* Elements are sorted by name ascending!
|
|
||||||
*/
|
|
||||||
'By "Latest" category' => array(
|
|
||||||
'latest_category' => array(
|
|
||||||
'name' => 'Latest category',
|
|
||||||
'type' => 'list',
|
|
||||||
'required' => true,
|
|
||||||
'title' => 'Select your category',
|
|
||||||
'defaultValue' => 'news',
|
|
||||||
'values' => array(
|
|
||||||
'Hot Torrents' => 'hot_torrents',
|
|
||||||
'News' => 'news',
|
|
||||||
'Releases' => 'releases',
|
|
||||||
'Torrents' => 'torrents'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get feeds for one of the "torrent" categories
|
|
||||||
* Make sure to add new categories also to get_torrent_category_index($)!
|
|
||||||
* Elements are sorted by name ascending!
|
|
||||||
*/
|
|
||||||
'By "Torrent" category' => array(
|
|
||||||
'torrent_category' => array(
|
|
||||||
'name' => 'Torrent category',
|
|
||||||
'type' => 'list',
|
|
||||||
'required' => true,
|
|
||||||
'title' => 'Select your category',
|
|
||||||
'defaultValue' => 'anime',
|
|
||||||
'values' => array(
|
|
||||||
'Adult' => 'adult',
|
|
||||||
'Anime' => 'anime',
|
|
||||||
'Books' => 'books',
|
|
||||||
'Games' => 'games',
|
|
||||||
'Movies' => 'movies',
|
|
||||||
'Music' => 'music',
|
|
||||||
'Other' => 'other',
|
|
||||||
'Series & TV' => 'series_tv',
|
|
||||||
'Software' => 'software'
|
|
||||||
)
|
|
||||||
),
|
|
||||||
'torrent_popularity' => array(
|
|
||||||
'name' => 'Sort by popularity',
|
|
||||||
'type' => 'checkbox',
|
|
||||||
'title' => 'Activate to receive results by popularity'
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get feeds for a specific search request
|
|
||||||
*/
|
|
||||||
'Search torrent by name' => array(
|
|
||||||
'search_name' => array(
|
|
||||||
'name' => 'Name',
|
|
||||||
'required' => true,
|
|
||||||
'title' => 'Insert your search query',
|
|
||||||
'exampleValue' => 'Bridge'
|
|
||||||
),
|
|
||||||
'search_category' => array(
|
|
||||||
'name' => 'Category',
|
|
||||||
'type' => 'list',
|
|
||||||
'title' => 'Select your category',
|
|
||||||
'defaultValue' => 'all',
|
|
||||||
'values' => array(
|
|
||||||
'Adult' => 'adult',
|
|
||||||
'All' => 'all',
|
|
||||||
'Anime' => 'anime',
|
|
||||||
'Books' => 'books',
|
|
||||||
'Games' => 'games',
|
|
||||||
'Movies' => 'movies',
|
|
||||||
'Music' => 'music',
|
|
||||||
'Other' => 'other',
|
|
||||||
'Series & TV' => 'series_tv',
|
|
||||||
'Software' => 'software'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
public function getURI(){
|
|
||||||
$uri = self::URI;
|
|
||||||
switch($this->queriedContext) {
|
|
||||||
case 'By "Latest" category':
|
|
||||||
switch($this->getInput('latest_category')) {
|
|
||||||
case 'hot_torrents':
|
|
||||||
$uri .= 'statistic/hot/torrents';
|
|
||||||
break;
|
|
||||||
case 'news':
|
|
||||||
break;
|
|
||||||
case 'releases':
|
|
||||||
$uri .= 'releases.php';
|
|
||||||
break;
|
|
||||||
case 'torrents':
|
|
||||||
$uri .= 'latest.php';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'By "Torrent" category':
|
|
||||||
$uri .= $this->buildCategoryUri(
|
|
||||||
$this->getInput('torrent_category'),
|
|
||||||
$this->getInput('torrent_popularity')
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'Search torrent by name':
|
|
||||||
$category = $this->getInput('search_category');
|
|
||||||
$uri .= $this->buildCategoryUri($category);
|
|
||||||
if($category !== 'movies')
|
|
||||||
$uri .= '&ihq=' . urlencode($this->getInput('search_name'));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: parent::getURI();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(){
|
|
||||||
switch($this->queriedContext) {
|
|
||||||
case 'By "Latest" category':
|
|
||||||
$categoryName = array_search(
|
|
||||||
$this->getInput('latest_category'),
|
|
||||||
self::PARAMETERS['By "Latest" category']['latest_category']['values']
|
|
||||||
);
|
|
||||||
$name = 'Latest ' . $categoryName . ' - ' . self::NAME;
|
|
||||||
break;
|
|
||||||
case 'By "Torrent" category':
|
|
||||||
$categoryName = array_search(
|
|
||||||
$this->getInput('torrent_category'),
|
|
||||||
self::PARAMETERS['By "Torrent" category']['torrent_category']['values']
|
|
||||||
);
|
|
||||||
$name = 'Category: ' . $categoryName . ' - ' . self::NAME;
|
|
||||||
break;
|
|
||||||
case 'Search torrent by name':
|
|
||||||
$categoryName = array_search(
|
|
||||||
$this->getInput('search_category'),
|
|
||||||
self::PARAMETERS['Search torrent by name']['search_category']['values']
|
|
||||||
);
|
|
||||||
$name = 'Search: "'
|
|
||||||
. $this->getInput('search_name')
|
|
||||||
. '" in category: '
|
|
||||||
. $categoryName . ' - '
|
|
||||||
. self::NAME;
|
|
||||||
break;
|
|
||||||
default: return parent::getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function collectData(){
|
|
||||||
$html = $this->loadHtml($this->getURI());
|
|
||||||
|
|
||||||
switch($this->queriedContext) {
|
|
||||||
case 'By "Latest" category':
|
|
||||||
switch($this->getInput('latest_category')) {
|
|
||||||
case 'hot_torrents':
|
|
||||||
$this->getLatestHotTorrents($html);
|
|
||||||
break;
|
|
||||||
case 'news':
|
|
||||||
$this->getLatestNews($html);
|
|
||||||
break;
|
|
||||||
case 'releases':
|
|
||||||
case 'torrents':
|
|
||||||
$this->getLatestTorrents($html);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'By "Torrent" category':
|
|
||||||
if($this->getInput('torrent_category') === 'movies') {
|
|
||||||
// This one is special (content wise)
|
|
||||||
$this->getMovieTorrents($html);
|
|
||||||
} else {
|
|
||||||
$this->getLatestTorrents($html);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'Search torrent by name':
|
|
||||||
if($this->getInput('search_category') === 'movies') {
|
|
||||||
// This one is special (content wise)
|
|
||||||
$this->getMovieTorrents($html);
|
|
||||||
} else {
|
|
||||||
$this->getLatestTorrents($html);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Helper functions for "Movie Torrents"
|
|
||||||
|
|
||||||
private function getMovieTorrents($html){
|
|
||||||
$container = $html->find('div#w0', 0);
|
|
||||||
if(!$container)
|
|
||||||
returnServerError('Unable to find torrent container!');
|
|
||||||
|
|
||||||
$torrents = $container->find('article');
|
|
||||||
if(!$torrents)
|
|
||||||
returnServerError('Unable to find torrents!');
|
|
||||||
|
|
||||||
foreach($torrents as $torrent) {
|
|
||||||
|
|
||||||
$anchor = $torrent->find('a', 0);
|
|
||||||
if(!$anchor)
|
|
||||||
returnServerError('Unable to find anchor!');
|
|
||||||
|
|
||||||
$date = $torrent->find('small', 0);
|
|
||||||
if(!$date)
|
|
||||||
returnServerError('Unable to find date!');
|
|
||||||
|
|
||||||
$item = array();
|
|
||||||
|
|
||||||
$item['uri'] = $this->fixRelativeUri($anchor->href);
|
|
||||||
$item['title'] = $anchor->title;
|
|
||||||
// $item['author'] =
|
|
||||||
$item['timestamp'] = strtotime($date->plaintext);
|
|
||||||
$item['content'] = $this->fixRelativeUri($torrent->innertext);
|
|
||||||
|
|
||||||
$this->items[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Helper functions for "Latest Hot Torrents"
|
|
||||||
|
|
||||||
private function getLatestHotTorrents($html){
|
|
||||||
$container = $html->find('div#serps', 0);
|
|
||||||
if(!$container)
|
|
||||||
returnServerError('Unable to find torrent container!');
|
|
||||||
|
|
||||||
$torrents = $container->find('tr');
|
|
||||||
if(!$torrents)
|
|
||||||
returnServerError('Unable to find torrents!');
|
|
||||||
|
|
||||||
// Remove first element (header row)
|
|
||||||
$torrents = array_slice($torrents, 1);
|
|
||||||
|
|
||||||
foreach($torrents as $torrent) {
|
|
||||||
|
|
||||||
$cell = $torrent->find('td', 0);
|
|
||||||
if(!$cell)
|
|
||||||
returnServerError('Unable to find cell!');
|
|
||||||
|
|
||||||
$element = $cell->find('a', 0);
|
|
||||||
if(!$element)
|
|
||||||
returnServerError('Unable to find element!');
|
|
||||||
|
|
||||||
$item = array();
|
|
||||||
|
|
||||||
$item['uri'] = $element->href;
|
|
||||||
$item['title'] = $element->plaintext;
|
|
||||||
// $item['author'] =
|
|
||||||
// $item['timestamp'] =
|
|
||||||
// $item['content'] =
|
|
||||||
|
|
||||||
$this->items[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Helper functions for "Latest News"
|
|
||||||
|
|
||||||
private function getLatestNews($html){
|
|
||||||
$container = $html->find('div#postcontainer', 0);
|
|
||||||
if(!$container)
|
|
||||||
returnServerError('Unable to find post container!');
|
|
||||||
|
|
||||||
$posts = $container->find('div.index-post');
|
|
||||||
if(!$posts)
|
|
||||||
returnServerError('Unable to find posts!');
|
|
||||||
|
|
||||||
foreach($posts as $post) {
|
|
||||||
$item = array();
|
|
||||||
|
|
||||||
$item['uri'] = $this->latestNewsExtractUri($post);
|
|
||||||
$item['title'] = $this->latestNewsExtractTitle($post);
|
|
||||||
$item['author'] = $this->latestNewsExtractAuthor($post);
|
|
||||||
$item['timestamp'] = $this->latestNewsExtractTimestamp($post);
|
|
||||||
$item['content'] = $this->latestNewsExtractContent($post);
|
|
||||||
|
|
||||||
$this->items[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function latestNewsExtractAuthor($post){
|
|
||||||
$author = $post->find('small', 0);
|
|
||||||
if(!$author)
|
|
||||||
returnServerError('Unable to find author!');
|
|
||||||
|
|
||||||
// The author is hidden within a string like: 'Posted by {author} on {date}'
|
|
||||||
preg_match('/Posted\sby\s(.*)\son/i', $author->innertext, $matches);
|
|
||||||
|
|
||||||
return $matches[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function latestNewsExtractTimestamp($post){
|
|
||||||
$date = $post->find('small', 0);
|
|
||||||
if(!$date)
|
|
||||||
returnServerError('Unable to find date!');
|
|
||||||
|
|
||||||
// The date is hidden within a string like: 'Posted by {author} on {date}'
|
|
||||||
preg_match('/Posted\sby\s.*\son\s(.*)/i', $date->innertext, $matches);
|
|
||||||
|
|
||||||
$timestamp = strtotime($matches[1]);
|
|
||||||
|
|
||||||
// Make sure date is not in the future (dates are given like 'Nov. 20' without year)
|
|
||||||
if($timestamp > time()) {
|
|
||||||
$timestamp = strtotime('-1 year', $timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function latestNewsExtractTitle($post){
|
|
||||||
$title = $post->find('a', 0);
|
|
||||||
if(!$title)
|
|
||||||
returnServerError('Unable to find title!');
|
|
||||||
|
|
||||||
return $title->plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function latestNewsExtractUri($post){
|
|
||||||
$uri = $post->find('a', 0);
|
|
||||||
if(!$uri)
|
|
||||||
returnServerError('Unable to find uri!');
|
|
||||||
|
|
||||||
return $uri->href;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function latestNewsExtractContent($post){
|
|
||||||
$content = $post->find('div', 0);
|
|
||||||
if(!$content)
|
|
||||||
returnServerError('Unable to find content!');
|
|
||||||
|
|
||||||
// Remove <h2>...</h2> (title)
|
|
||||||
foreach($content->find('h2') as $element) {
|
|
||||||
$element->outertext = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove <small>...</small> (author)
|
|
||||||
foreach($content->find('small') as $element) {
|
|
||||||
$element->outertext = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $content->innertext;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Helper functions for "Latest Torrents", "Latest Releases" and "Torrent Category"
|
|
||||||
|
|
||||||
private function getLatestTorrents($html){
|
|
||||||
$container = $html->find('div#serps', 0);
|
|
||||||
if(!$container)
|
|
||||||
returnServerError('Unable to find torrent container!');
|
|
||||||
|
|
||||||
$torrents = $container->find('tr[data-key]');
|
|
||||||
if(!$torrents)
|
|
||||||
returnServerError('Unable to find torrents!');
|
|
||||||
|
|
||||||
foreach($torrents as $torrent) {
|
|
||||||
$item = array();
|
|
||||||
|
|
||||||
$item['uri'] = $this->latestTorrentsExtractUri($torrent);
|
|
||||||
$item['title'] = $this->latestTorrentsExtractTitle($torrent);
|
|
||||||
$item['author'] = $this->latestTorrentsExtractAuthor($torrent);
|
|
||||||
$item['timestamp'] = $this->latestTorrentsExtractTimestamp($torrent);
|
|
||||||
$item['content'] = ''; // There is no valuable content
|
|
||||||
|
|
||||||
$this->items[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function latestTorrentsExtractTitle($torrent){
|
|
||||||
$cell = $torrent->find('td.title-row', 0);
|
|
||||||
if(!$cell)
|
|
||||||
returnServerError('Unable to find title cell!');
|
|
||||||
|
|
||||||
$title = $cell->find('span', 0);
|
|
||||||
if(!$title)
|
|
||||||
returnServerError('Unable to find title!');
|
|
||||||
|
|
||||||
return $title->plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function latestTorrentsExtractUri($torrent){
|
|
||||||
$cell = $torrent->find('td.title-row', 0);
|
|
||||||
if(!$cell)
|
|
||||||
returnServerError('Unable to find title cell!');
|
|
||||||
|
|
||||||
$uri = $cell->find('a', 0);
|
|
||||||
if(!$uri)
|
|
||||||
returnServerError('Unable to find uri!');
|
|
||||||
|
|
||||||
return $this->fixRelativeUri($uri->href);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function latestTorrentsExtractAuthor($torrent){
|
|
||||||
$cell = $torrent->find('td.user-row', 0);
|
|
||||||
if(!$cell)
|
|
||||||
return; // No author
|
|
||||||
|
|
||||||
$user = $cell->find('a', 0);
|
|
||||||
if(!$user)
|
|
||||||
returnServerError('Unable to find user!');
|
|
||||||
|
|
||||||
return $user->plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function latestTorrentsExtractTimestamp($torrent){
|
|
||||||
$cell = $torrent->find('td.date-row', 0);
|
|
||||||
if(!$cell)
|
|
||||||
returnServerError('Unable to find date cell!');
|
|
||||||
|
|
||||||
return strtotime('-' . $cell->plaintext, time());
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Generic helper functions
|
|
||||||
|
|
||||||
private function loadHtml($uri){
|
|
||||||
$html = getSimpleHTMLDOM($uri);
|
|
||||||
if(!$html)
|
|
||||||
returnServerError('Unable to load ' . $uri . '!');
|
|
||||||
|
|
||||||
return $html;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fixRelativeUri($uri){
|
|
||||||
return preg_replace('/\//i', self::URI, $uri, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function buildCategoryUri($category, $order_popularity = false){
|
|
||||||
switch($category) {
|
|
||||||
case 'anime': $index = 1; break;
|
|
||||||
case 'software' : $index = 2; break;
|
|
||||||
case 'games' : $index = 3; break;
|
|
||||||
case 'adult' : $index = 4; break;
|
|
||||||
case 'movies' : $index = 5; break;
|
|
||||||
case 'music' : $index = 6; break;
|
|
||||||
case 'other' : $index = 7; break;
|
|
||||||
case 'series_tv' : $index = 8; break;
|
|
||||||
case 'books': $index = 9; break;
|
|
||||||
case 'all':
|
|
||||||
default: $index = 0; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'torrents/?iht=' . $index . '&ihs=' . ($order_popularity ? 1 : 0) . '&age=0';
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
353
bridges/JustETFBridge.php
Normal file
353
bridges/JustETFBridge.php
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
<?php
|
||||||
|
class JustETFBridge extends BridgeAbstract {
|
||||||
|
const NAME = 'justETF Bridge';
|
||||||
|
const URI = 'https://www.justetf.com';
|
||||||
|
const DESCRIPTION = 'Currently only supports the news feed';
|
||||||
|
const MAINTAINER = 'logmanoriginal';
|
||||||
|
const PARAMETERS = array(
|
||||||
|
'News' => array(
|
||||||
|
'full' => array(
|
||||||
|
'name' => 'Full Article',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'title' => 'Enable to load full articles'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'Profile' => array(
|
||||||
|
'isin' => array(
|
||||||
|
'name' => 'ISIN',
|
||||||
|
'type' => 'text',
|
||||||
|
'required' => true,
|
||||||
|
'pattern' => '[a-zA-Z]{2}[a-zA-Z0-9]{10}',
|
||||||
|
'title' => 'ISIN, consisting of 2-letter country code, 9-character identifier, check character'
|
||||||
|
),
|
||||||
|
'strategy' => array(
|
||||||
|
'name' => 'Include Strategy',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'defaultValue' => 'checked'
|
||||||
|
),
|
||||||
|
'description' => array(
|
||||||
|
'name' => 'Include Description',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'defaultValue' => 'checked'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'global' => array(
|
||||||
|
'lang' => array(
|
||||||
|
'name' => 'Language',
|
||||||
|
'required' => true,
|
||||||
|
'type' => 'list',
|
||||||
|
'values' => array(
|
||||||
|
'Englisch' => 'en',
|
||||||
|
'Deutsch' => 'de',
|
||||||
|
'Italiano' => 'it'
|
||||||
|
),
|
||||||
|
'defaultValue' => 'Englisch'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
$html = getSimpleHTMLDOM($this->getURI())
|
||||||
|
or returnServerError('Failed loading contents from ' . $this->getURI());
|
||||||
|
|
||||||
|
defaultLinkTo($html, static::URI);
|
||||||
|
|
||||||
|
switch($this->queriedContext) {
|
||||||
|
case 'News':
|
||||||
|
$this->collectNews($html);
|
||||||
|
break;
|
||||||
|
case 'Profile':
|
||||||
|
$this->collectProfile($html);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getURI() {
|
||||||
|
$uri = static::URI;
|
||||||
|
|
||||||
|
if($this->getInput('lang')) {
|
||||||
|
$uri .= '/' . $this->getInput('lang');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch($this->queriedContext) {
|
||||||
|
case 'News':
|
||||||
|
$uri .= '/news';
|
||||||
|
break;
|
||||||
|
case 'Profile':
|
||||||
|
$uri .= '/etf-profile.html?' . http_build_query(array(
|
||||||
|
'isin' => strtoupper($this->getInput('isin'))
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName() {
|
||||||
|
$name = static::NAME;
|
||||||
|
|
||||||
|
$name .= ($this->queriedContext) ? ' - ' . $this->queriedContext : '';
|
||||||
|
|
||||||
|
switch($this->queriedContext) {
|
||||||
|
case 'News': break;
|
||||||
|
case 'Profile':
|
||||||
|
if($this->getInput('isin')) {
|
||||||
|
$name .= ' ISIN ' . strtoupper($this->getInput('isin'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->getInput('lang')) {
|
||||||
|
$name .= ' (' . strtoupper($this->getInput('lang')) . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Common
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixes dates depending on the choosen language:
|
||||||
|
*
|
||||||
|
* de : dd.mm.yy
|
||||||
|
* en : dd.mm.yy
|
||||||
|
* it : dd/mm/yy
|
||||||
|
*
|
||||||
|
* Basically strtotime doesn't convert dates correctly due to formats
|
||||||
|
* being hard to interpret. So we use the DateTime object, manually
|
||||||
|
* fixing dates and times (set to 00:00:00.000).
|
||||||
|
*
|
||||||
|
* We don't know the timezone, so just assume +00:00 (or whatever
|
||||||
|
* DateTime chooses)
|
||||||
|
*/
|
||||||
|
private function fixDate($date) {
|
||||||
|
switch($this->getInput('lang')) {
|
||||||
|
case 'en':
|
||||||
|
case 'de':
|
||||||
|
$df = date_create_from_format('d.m.y', $date);
|
||||||
|
break;
|
||||||
|
case 'it':
|
||||||
|
$df = date_create_from_format('d/m/y', $date);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
date_time_set($df, 0, 0);
|
||||||
|
|
||||||
|
// debugMessage(date_format($df, 'U'));
|
||||||
|
|
||||||
|
return date_format($df, 'U');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractImages($article) {
|
||||||
|
// Notice: We can have zero or more images (though it should mostly be 1)
|
||||||
|
$elements = $article->find('img');
|
||||||
|
|
||||||
|
$images = array();
|
||||||
|
|
||||||
|
foreach($elements as $img) {
|
||||||
|
// Skip the logo (mostly provided part of a hidden div)
|
||||||
|
if(substr($img->src, strrpos($img->src, '/') + 1) === 'logo.png')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$images[] = $img->src;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $images;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region News
|
||||||
|
|
||||||
|
private function collectNews($html) {
|
||||||
|
$articles = $html->find('div.newsTopArticle')
|
||||||
|
or returnServerError('No articles found! Layout might have changed!');
|
||||||
|
|
||||||
|
foreach($articles as $article) {
|
||||||
|
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
// Common data
|
||||||
|
|
||||||
|
$item['uri'] = $this->extractNewsUri($article);
|
||||||
|
$item['timestamp'] = $this->extractNewsDate($article);
|
||||||
|
$item['title'] = $this->extractNewsTitle($article);
|
||||||
|
|
||||||
|
if($this->getInput('full')) {
|
||||||
|
|
||||||
|
$uri = $this->extractNewsUri($article);
|
||||||
|
|
||||||
|
$html = getSimpleHTMLDOMCached($uri)
|
||||||
|
or returnServerError('Failed loading full article from ' . $uri);
|
||||||
|
|
||||||
|
$fullArticle = $html->find('div.article', 0)
|
||||||
|
or returnServerError('No content found! Layout might have changed!');
|
||||||
|
|
||||||
|
defaultLinkTo($fullArticle, static::URI);
|
||||||
|
|
||||||
|
$item['author'] = $this->extractFullArticleAuthor($fullArticle);
|
||||||
|
$item['content'] = $this->extractFullArticleContent($fullArticle);
|
||||||
|
$item['enclosures'] = $this->extractImages($fullArticle);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$item['content'] = $this->extractNewsDescription($article);
|
||||||
|
$item['enclosures'] = $this->extractImages($article);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractNewsUri($article) {
|
||||||
|
$element = $article->find('a', 0)
|
||||||
|
or returnServerError('Anchor not found!');
|
||||||
|
|
||||||
|
return $element->href;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractNewsDate($article) {
|
||||||
|
$element = $article->find('div.subheadline', 0)
|
||||||
|
or returnServerError('Date not found!');
|
||||||
|
|
||||||
|
// debugMessage($element->plaintext);
|
||||||
|
|
||||||
|
$date = trim(explode('|', $element->plaintext)[0]);
|
||||||
|
|
||||||
|
return $this->fixDate($date);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractNewsDescription($article) {
|
||||||
|
$element = $article->find('span.newsText', 0)
|
||||||
|
or returnServerError('Description not found!');
|
||||||
|
|
||||||
|
$element->find('a', 0)->onclick = '';
|
||||||
|
|
||||||
|
// debugMessage($element->innertext);
|
||||||
|
|
||||||
|
return $element->innertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractNewsTitle($article) {
|
||||||
|
$element = $article->find('h3', 0)
|
||||||
|
or returnServerError('Title not found!');
|
||||||
|
|
||||||
|
return $element->plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractFullArticleContent($article) {
|
||||||
|
$element = $article->find('div.article_body', 0)
|
||||||
|
or returnServerError('Article body not found!');
|
||||||
|
|
||||||
|
// Remove teaser image
|
||||||
|
$element->find('img.teaser-img', 0)->outertext = '';
|
||||||
|
|
||||||
|
// Remove self advertisements
|
||||||
|
foreach($element->find('.call-action') as $adv) {
|
||||||
|
$adv->outertext = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove tips
|
||||||
|
foreach($element->find('.panel-edu') as $tip) {
|
||||||
|
$tip->outertext = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove inline scripts (used for i.e. interactive graphs) as they are
|
||||||
|
// rendered as a long series of strings
|
||||||
|
foreach($element->find('script') as $script) {
|
||||||
|
$script->outertext = '[Content removed! Visit site to see full contents!]';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $element->innertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractFullArticleAuthor($article) {
|
||||||
|
$element = $article->find('span[itemprop=name]', 0)
|
||||||
|
or returnServerError('Author not found!');
|
||||||
|
|
||||||
|
return $element->plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Profile
|
||||||
|
|
||||||
|
private function collectProfile($html) {
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
$item['uri'] = $this->getURI();
|
||||||
|
$item['timestamp'] = $this->extractProfileDate($html);
|
||||||
|
$item['title'] = $this->extractProfiletitle($html);
|
||||||
|
$item['author'] = $this->extractProfileAuthor($html);
|
||||||
|
$item['content'] = $this->extractProfileContent($html);
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractProfileDate($html) {
|
||||||
|
$element = $html->find('div.infobox div.vallabel', 0)
|
||||||
|
or returnServerError('Date not found!');
|
||||||
|
|
||||||
|
// debugMessage($element->plaintext);
|
||||||
|
|
||||||
|
$date = trim(explode("\r\n", $element->plaintext)[1]);
|
||||||
|
|
||||||
|
return $this->fixDate($date);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractProfileTitle($html) {
|
||||||
|
$element = $html->find('span.h1', 0)
|
||||||
|
or returnServerError('Title not found!');
|
||||||
|
|
||||||
|
return $element->plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractProfileContent($html) {
|
||||||
|
// There are a few thins we are interested:
|
||||||
|
// - Investment Strategy
|
||||||
|
// - Description
|
||||||
|
// - Quote
|
||||||
|
|
||||||
|
$strategy = $html->find('div.tab-container div.col-sm-6 p', 0)
|
||||||
|
or returnServerError('Investment Strategy not found!');
|
||||||
|
|
||||||
|
// Description requires a bit of cleanup due to lack of propper identification
|
||||||
|
|
||||||
|
$description = $html->find('div.headline', 5)
|
||||||
|
or returnServerError('Description container not found!');
|
||||||
|
|
||||||
|
$description = $description->parent();
|
||||||
|
|
||||||
|
foreach($description->find('div') as $div) {
|
||||||
|
$div->outertext = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$quote = $html->find('div.infobox div.val', 0)
|
||||||
|
or returnServerError('Quote not found!');
|
||||||
|
|
||||||
|
$quote_html = '<strong>Quote</strong><br><p>' . $quote . '</p>';
|
||||||
|
$strategy_html = '';
|
||||||
|
$description_html = '';
|
||||||
|
|
||||||
|
if($this->getInput('strategy') === true) {
|
||||||
|
$strategy_html = '<strong>Strategy</strong><br><p>' . $strategy . '</p><br>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->getInput('description') === true) {
|
||||||
|
$description_html = '<strong>Description</strong><br><p>' . $description . '</p><br>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $strategy_html . $description_html . $quote_html;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractProfileAuthor($html) {
|
||||||
|
// Use ISIN + WKN as author
|
||||||
|
// Notice: "identfier" is not a typo [sic]!
|
||||||
|
$element = $html->find('span.identfier', 0)
|
||||||
|
or returnServerError('Author not found!');
|
||||||
|
|
||||||
|
return $element->plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
@@ -58,7 +58,7 @@ class KununuBridge extends BridgeAbstract {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::URI . $site . '/' . $company . '/' . $section;
|
return self::URI . $site . '/' . $company . '/' . $section . '?sort=update_time_desc';
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::getURI();
|
return parent::getURI();
|
||||||
@@ -135,8 +135,8 @@ class KununuBridge extends BridgeAbstract {
|
|||||||
* Encodes unmlauts in the given text
|
* Encodes unmlauts in the given text
|
||||||
*/
|
*/
|
||||||
private function encodeUmlauts($text){
|
private function encodeUmlauts($text){
|
||||||
$umlauts = Array("/ä/","/ö/","/ü/","/Ä/","/Ö/","/Ü/","/ß/");
|
$umlauts = Array('/ä/','/ö/','/ü/','/Ä/','/Ö/','/Ü/','/ß/');
|
||||||
$replace = Array("ae","oe","ue","Ae","Oe","Ue","ss");
|
$replace = Array('ae','oe','ue','Ae','Oe','Ue','ss');
|
||||||
|
|
||||||
return preg_replace($umlauts, $replace, $text);
|
return preg_replace($umlauts, $replace, $text);
|
||||||
}
|
}
|
||||||
@@ -190,11 +190,11 @@ class KununuBridge extends BridgeAbstract {
|
|||||||
* Returns the URI from a given article
|
* Returns the URI from a given article
|
||||||
*/
|
*/
|
||||||
private function extractArticleUri($article){
|
private function extractArticleUri($article){
|
||||||
$anchor = $article->find('ku-company-review-more', 0);
|
$anchor = $article->find('h1.review-title a', 0);
|
||||||
if(is_null($anchor))
|
if(is_null($anchor))
|
||||||
returnServerError('Cannot find article URI!');
|
returnServerError('Cannot find article URI!');
|
||||||
|
|
||||||
return self::URI . $anchor->{'review-url'};
|
return self::URI . $anchor->href;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
class LeBonCoinBridge extends BridgeAbstract {
|
class LeBonCoinBridge extends BridgeAbstract {
|
||||||
|
|
||||||
const MAINTAINER = '16mhz';
|
const MAINTAINER = 'jacknumber';
|
||||||
const NAME = 'LeBonCoin';
|
const NAME = 'LeBonCoin';
|
||||||
const URI = 'https://www.leboncoin.fr/';
|
const URI = 'https://www.leboncoin.fr/';
|
||||||
const DESCRIPTION = 'Returns most recent results from LeBonCoin for a
|
const DESCRIPTION = 'Returns most recent results from LeBonCoin';
|
||||||
region, and optionally a category and a keyword .';
|
|
||||||
|
|
||||||
const PARAMETERS = array(
|
const PARAMETERS = array(
|
||||||
array(
|
array(
|
||||||
@@ -14,125 +13,138 @@ region, and optionally a category and a keyword .';
|
|||||||
'name' => 'Région',
|
'name' => 'Région',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'values' => array(
|
'values' => array(
|
||||||
'Toute la France' => 'ile_de_france/occasions',
|
'Toute la France' => '',
|
||||||
'Alsace' => 'alsace',
|
'Alsace' => '1',
|
||||||
'Aquitaine' => 'aquitaine',
|
'Aquitaine' => '2',
|
||||||
'Auvergne' => 'auvergne',
|
'Auvergne' => '3',
|
||||||
'Basse Normandie' => 'basse_normandie',
|
'Basse Normandie' => '4',
|
||||||
'Bourgogne' => 'bourgogne',
|
'Bourgogne' => '5',
|
||||||
'Bretagne' => 'bretagne',
|
'Bretagne' => '6',
|
||||||
'Centre' => 'centre',
|
'Centre' => '7',
|
||||||
'Champagne Ardenne' => 'champagne_ardenne',
|
'Champagne Ardenne' => '8',
|
||||||
'Corse' => 'corse',
|
'Corse' => '9',
|
||||||
'Franche Comté' => 'franche_comte',
|
'Franche Comté' => '10',
|
||||||
'Haute Normandie' => 'haute_normandie',
|
'Haute Normandie' => '11',
|
||||||
'Ile de France' => 'ile_de_france',
|
'Ile de France' => '12',
|
||||||
'Languedoc Roussillon' => 'languedoc_roussillon',
|
'Languedoc Roussillon' => '13',
|
||||||
'Limousin' => 'limousin',
|
'Limousin' => '14',
|
||||||
'Lorraine' => 'lorraine',
|
'Lorraine' => '15',
|
||||||
'Midi Pyrénées' => 'midi_pyrenees',
|
'Midi Pyrénées' => '16',
|
||||||
'Nord Pas De Calais' => 'nord_pas_de_calais',
|
'Nord Pas De Calais' => '17',
|
||||||
'Pays de la Loire' => 'pays_de_la_loire',
|
'Pays de la Loire' => '18',
|
||||||
'Picardie' => 'picardie',
|
'Picardie' => '19',
|
||||||
'Poitou Charentes' => 'poitou_charentes',
|
'Poitou Charentes' => '20',
|
||||||
'Provence Alpes Côte d\'Azur' => 'provence_alpes_cote_d_azur',
|
'Provence Alpes Côte d\'Azur' => '21',
|
||||||
'Rhône-Alpes' => 'rhone_alpes',
|
'Rhône-Alpes' => '22',
|
||||||
'Guadeloupe' => 'guadeloupe',
|
'Guadeloupe' => '23',
|
||||||
'Martinique' => 'martinique',
|
'Martinique' => '24',
|
||||||
'Guyane' => 'guyane',
|
'Guyane' => '25',
|
||||||
'Réunion' => 'reunion'
|
'Réunion' => '26'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
'cities' => array('name' => 'Ville'),
|
||||||
'c' => array(
|
'c' => array(
|
||||||
'name' => 'Catégorie',
|
'name' => 'Catégorie',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'values' => array(
|
'values' => array(
|
||||||
'TOUS' => '',
|
'Toutes catégories' => '',
|
||||||
'EMPLOI' => '_emploi_',
|
'EMPLOI' => array(
|
||||||
|
'Emploi et recrutement' => '71',
|
||||||
|
'Offres d\'emploi et jobs' => '33'
|
||||||
|
),
|
||||||
'VEHICULES' => array(
|
'VEHICULES' => array(
|
||||||
'Tous' => '_vehicules_',
|
'Tous' => '1',
|
||||||
'Voitures' => 'voitures',
|
'Voitures' => '2',
|
||||||
'Motos' => 'motos',
|
'Motos' => '3',
|
||||||
'Caravaning' => 'caravaning',
|
'Caravaning' => '4',
|
||||||
'Utilitaires' => 'utilitaires',
|
'Utilitaires' => '5',
|
||||||
'Équipement Auto' => 'equipement_auto',
|
'Equipement Auto' => '6',
|
||||||
'Équipement Moto' => 'equipement_moto',
|
'Equipement Moto' => '44',
|
||||||
'Équipement Caravaning' => 'equipement_caravaning',
|
'Equipement Caravaning' => '50',
|
||||||
'Nautisme' => 'nautisme',
|
'Nautisme' => '7',
|
||||||
'Équipement Nautisme' => 'equipement_nautisme'
|
'Equipement Nautisme' => '51'
|
||||||
),
|
),
|
||||||
'IMMOBILIER' => array(
|
'IMMOBILIER' => array(
|
||||||
'Tous' => '_immobilier_',
|
'Tous' => '8',
|
||||||
'Ventes immobilières' => 'ventes_immobilieres',
|
'Ventes immobilières' => '9',
|
||||||
'Locations' => 'locations',
|
'Locations' => '10',
|
||||||
'Colocations' => 'colocations',
|
'Colocations' => '11',
|
||||||
'Bureaux & Commerces' => 'bureaux_commerces'
|
'Bureaux & Commerces' => '13'
|
||||||
),
|
),
|
||||||
'VACANCES' => array(
|
'VACANCES' => array(
|
||||||
'Tous' => '_vacances_',
|
'Tous' => '66',
|
||||||
'Location gîtes' => 'locations_gites',
|
'Locations & Gîtes' => '12',
|
||||||
'Chambres d\'hôtes' => 'chambres_d_hotes',
|
'Chambres d\'hôtes' => '67',
|
||||||
'Campings' => 'campings',
|
'Campings' => '68',
|
||||||
'Hôtels' => 'hotels',
|
'Hôtels' => '69',
|
||||||
'Hébergements insolites' => 'hebergements_insolites'
|
'Hébergements insolites' => '70'
|
||||||
),
|
),
|
||||||
'MULTIMEDIA' => array(
|
'MULTIMEDIA' => array(
|
||||||
'Tous' => '_multimedia_',
|
'Tous' => '14',
|
||||||
'Informatique' => 'informatique',
|
'Informatique' => '15',
|
||||||
'Consoles & Jeux vidéo' => 'consoles_jeux_video',
|
'Consoles & Jeux vidéo' => '43',
|
||||||
'Image & Son' => 'image_son',
|
'Image & Son' => '16',
|
||||||
'Téléphonie' => 'telephonie'
|
'Téléphonie' => '17'
|
||||||
),
|
),
|
||||||
'LOISIRS' => array(
|
'LOISIRS' => array(
|
||||||
'Tous' => '_loisirs_',
|
'Tous' => '24',
|
||||||
'DVD / Films' => 'dvd_films',
|
'DVD / Films' => '25',
|
||||||
'CD / Musique' => 'cd_musique',
|
'CD / Musique' => '26',
|
||||||
'Livres' => 'livres',
|
'Livres' => '27',
|
||||||
'Animaux' => 'animaux',
|
'Animaux' => '28',
|
||||||
'Vélos' => 'velos',
|
'Vélos' => '55',
|
||||||
'Sports & Hobbies' => 'sports_hobbies',
|
'Sports & Hobbies' => '29',
|
||||||
'Instruments de musique' => 'instruments_de_musique',
|
'Instruments de musique' => '30',
|
||||||
'Collection' => 'collection',
|
'Collection' => '40',
|
||||||
'Jeux & Jouets' => 'jeux_jouets',
|
'Jeux & Jouets' => '41',
|
||||||
'Vins & Gastronomie' => 'vins_gastronomie'
|
'Vins & Gastronomie' => '48'
|
||||||
),
|
),
|
||||||
'MATÉRIEL PROFESSIONNEL' => array(
|
'MATERIEL PROFESSIONNEL' => array(
|
||||||
'Tous' => '_materiel_professionnel_',
|
'Tous' => '56',
|
||||||
'Matériel Agricole' => 'mateiel_agricole',
|
'Matériel Agricole' => '57',
|
||||||
'Transport - Manutention' => 'transport_manutention',
|
'Transport - Manutention' => '58',
|
||||||
'BTP - Chantier - Gros-œuvre' => 'btp_chantier_gros_oeuvre',
|
'BTP - Chantier Gros-oeuvre' => '59',
|
||||||
'Outillage - Matériaux 2nd-œuvre' => 'outillage_materiaux_2nd_oeuvre',
|
'Outillage - Matériaux 2nd-oeuvre' => '60',
|
||||||
'Équipements Industriels' => 'equipement_industriels',
|
'Équipements Industriels' => '32',
|
||||||
'Restauration - Hôtellerie' => 'restauration_hotellerie',
|
'Restauration - Hôtellerie' => '61',
|
||||||
'Fournitures de Bureau' => 'fournitures_de_bureau',
|
'Fournitures de Bureau' => '62',
|
||||||
'Commerces & Marchés' => 'commerces_marches',
|
'Commerces & Marchés' => '63',
|
||||||
'Matériel médical' => 'materiel_medical'
|
'Matériel Médical' => '64'
|
||||||
),
|
),
|
||||||
'SERVICES' => array(
|
'SERVICES' => array(
|
||||||
'Tous' => '_services_',
|
'Tous' => '31',
|
||||||
'Prestations de services' => 'prestations_de_services',
|
'Prestations de services' => '34',
|
||||||
'Billetterie' => 'billetterie',
|
'Billetterie' => '35',
|
||||||
'Évènements' => 'evenements',
|
'Evénements' => '49',
|
||||||
'Cours particuliers' => 'cours_particuliers',
|
'Cours particuliers' => '36',
|
||||||
'Covoiturage' => 'covoiturage'
|
'Covoiturage' => '65'
|
||||||
),
|
),
|
||||||
'MAISON' => array(
|
'MAISON' => array(
|
||||||
'Tous' => '_maison_',
|
'Tous' => '18',
|
||||||
'Ameublement' => 'ameublement',
|
'Ameublement' => '19',
|
||||||
'Électroménager' => 'electromenager',
|
'Electroménager' => '20',
|
||||||
'Arts de la table' => 'arts_de_la_table',
|
'Arts de la table' => '45',
|
||||||
'Décoration' => 'decoration',
|
'Décoration' => '39',
|
||||||
'Linge de maison' => 'linge_de_maison',
|
'Linge de maison' => '46',
|
||||||
'Bricolage' => 'bricolage',
|
'Bricolage' => '21',
|
||||||
'Jardinage' => 'jardinage',
|
'Jardinage' => '52',
|
||||||
'Vêtements' => 'vetements',
|
'Vêtements' => '22',
|
||||||
'Chaussures' => 'chaussures',
|
'Chaussures' => '53',
|
||||||
'Accessoires & Bagagerie' => 'accessoires_bagagerie',
|
'Accessoires & Bagagerie' => '47',
|
||||||
'Montres & Bijoux' => 'montres_bijoux',
|
'Montres & Bijoux' => '42',
|
||||||
'Équipement bébé' => 'equipement_bebe',
|
'Equipement bébé' => '23',
|
||||||
'Vêtements bébé' => 'vetements_bebe'
|
'Vêtements bébé' => '54',
|
||||||
),
|
),
|
||||||
'AUTRES' => 'autres'
|
'AUTRES' => '37'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'o' => array(
|
||||||
|
'name' => 'Vendeur',
|
||||||
|
'type' => 'list',
|
||||||
|
'values' => array(
|
||||||
|
'Tous' => '',
|
||||||
|
'Particuliers' => 'private',
|
||||||
|
'Professionnels' => 'pro',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -140,50 +152,72 @@ region, and optionally a category and a keyword .';
|
|||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
|
|
||||||
$category = $this->getInput('c');
|
$params = array(
|
||||||
if(empty($category)) {
|
'text' => $this->getInput('k'),
|
||||||
$category = 'annonces';
|
'region' => $this->getInput('r'),
|
||||||
|
'cities' => $this->getInput('cities'),
|
||||||
|
'category' => $this->getInput('c'),
|
||||||
|
'owner_type' => $this->getInput('o'),
|
||||||
|
);
|
||||||
|
|
||||||
|
$url = self::URI . 'recherche/?' . http_build_query($params);
|
||||||
|
$html = getContents($url)
|
||||||
|
or returnServerError('Could not request LeBonCoin. Tried: ' . $url);
|
||||||
|
|
||||||
|
if(!preg_match('/^<script>window.FLUX_STATE[^\r\n]*/m', $html, $matches)) {
|
||||||
|
returnServerError('Could not parse JSON in page content.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$html = getSimpleHTMLDOM(self::URI
|
$clean_match = str_replace(
|
||||||
. $category
|
array('</script>', '<script>window.FLUX_STATE = '),
|
||||||
. '/offres/'
|
array('', ''),
|
||||||
. $this->getInput('r')
|
$matches[0]
|
||||||
. '/?f=a&th=1&q='
|
);
|
||||||
. urlencode($this->getInput('k')))
|
$json = json_decode($clean_match);
|
||||||
or returnServerError('Could not request LeBonCoin.');
|
|
||||||
|
|
||||||
$list = $html->find('.tabsContent', 0);
|
if($json->adSearch->data->total === 0) {
|
||||||
if($list === null) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tags = $list->find('li');
|
foreach($json->adSearch->data->ads as $element) {
|
||||||
|
|
||||||
foreach($tags as $element) {
|
$item['title'] = $element->subject;
|
||||||
|
$item['content'] = $element->body;
|
||||||
|
$item['date'] = $element->index_date;
|
||||||
|
$item['timestamp'] = strtotime($element->index_date);
|
||||||
|
$item['uri'] = $element->url;
|
||||||
|
$item['ad_type'] = $element->ad_type;
|
||||||
|
$item['author'] = $element->owner->name;
|
||||||
|
|
||||||
$element = $element->find('a', 0);
|
if(isset($element->location->city)) {
|
||||||
|
|
||||||
$item = array();
|
$item['city'] = $element->location->city;
|
||||||
$item['uri'] = $element->href;
|
$item['content'] .= ' -- ' . $element->location->city;
|
||||||
$title = html_entity_decode($element->getAttribute('title'));
|
|
||||||
$content_image = $element->find('div.item_image', 0)->find('.lazyload', 0);
|
|
||||||
|
|
||||||
if($content_image !== null) {
|
|
||||||
$content = '<img src="' . $content_image->getAttribute('data-imgsrc') . '" alt="thumbnail">';
|
|
||||||
} else {
|
|
||||||
$content = "";
|
|
||||||
}
|
}
|
||||||
$date = $element->find('aside.item_absolute', 0)->find('p.item_sup', 0);
|
|
||||||
|
|
||||||
$detailsList = $element->find('section.item_infos', 0);
|
if(isset($element->location->zipcode)) {
|
||||||
|
$item['zipcode'] = $element->location->zipcode;
|
||||||
|
}
|
||||||
|
|
||||||
for($i = 0; $i <= 1; $i++) $content .= $detailsList->find('p.item_supp', $i)->plaintext;
|
if(isset($element->price)) {
|
||||||
$price = $detailsList->find('h3.item_price', 0);
|
|
||||||
$content .= $price === null ? '' : $price->plaintext;
|
$item['price'] = $element->price[0];
|
||||||
|
$item['content'] .= ' -- ' . current($element->price) . '€';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($element->images->urls)) {
|
||||||
|
|
||||||
|
$item['thumbnail'] = $element->images->thumb_url;
|
||||||
|
$item['enclosures'] = array();
|
||||||
|
|
||||||
|
foreach($element->images->urls as $image) {
|
||||||
|
$item['enclosures'][] = $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
$item['title'] = $title;
|
|
||||||
$item['content'] = $content . $date;
|
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,16 +22,16 @@ class LesJoiesDuCodeBridge extends BridgeAbstract {
|
|||||||
// retrieve .gif instead of static .jpg
|
// retrieve .gif instead of static .jpg
|
||||||
$images = $temp->find('p img');
|
$images = $temp->find('p img');
|
||||||
foreach($images as $image) {
|
foreach($images as $image) {
|
||||||
$img_src = str_replace(".jpg", ".gif", $image->src);
|
$img_src = str_replace('.jpg', '.gif', $image->src);
|
||||||
$image->src = $img_src;
|
$image->src = $img_src;
|
||||||
}
|
}
|
||||||
$content = $temp->innertext;
|
$content = $temp->innertext;
|
||||||
|
|
||||||
$auteur = $temp->find('i', 0);
|
$auteur = $temp->find('i', 0);
|
||||||
$pos = strpos($auteur->innertext, "by");
|
$pos = strpos($auteur->innertext, 'by');
|
||||||
|
|
||||||
if($pos > 0) {
|
if($pos > 0) {
|
||||||
$auteur = trim(str_replace("*/", "", substr($auteur->innertext, ($pos + 2))));
|
$auteur = trim(str_replace('*/', '', substr($auteur->innertext, ($pos + 2))));
|
||||||
$item['author'] = $auteur;
|
$item['author'] = $auteur;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -100,7 +100,7 @@ class MangareaderBridge extends BridgeAbstract {
|
|||||||
case 'Get popular mangas':
|
case 'Get popular mangas':
|
||||||
// Find manga name within "Popular mangas for ..."
|
// Find manga name within "Popular mangas for ..."
|
||||||
$pagetitle = $xpath->query(".//*[@id='bodyalt']/h1")->item(0)->nodeValue;
|
$pagetitle = $xpath->query(".//*[@id='bodyalt']/h1")->item(0)->nodeValue;
|
||||||
$this->request = substr($pagetitle, 0, strrpos($pagetitle, " -"));
|
$this->request = substr($pagetitle, 0, strrpos($pagetitle, ' -'));
|
||||||
$this->getPopularMangas($xpath);
|
$this->getPopularMangas($xpath);
|
||||||
break;
|
break;
|
||||||
case 'Get manga updates':
|
case 'Get manga updates':
|
||||||
@@ -120,7 +120,7 @@ class MangareaderBridge extends BridgeAbstract {
|
|||||||
// Return some dummy-data if no content available
|
// Return some dummy-data if no content available
|
||||||
if(empty($this->items)) {
|
if(empty($this->items)) {
|
||||||
$item = array();
|
$item = array();
|
||||||
$item['content'] = "<p>No updates available</p>";
|
$item['content'] = '<p>No updates available</p>';
|
||||||
|
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
}
|
}
|
||||||
@@ -143,18 +143,18 @@ class MangareaderBridge extends BridgeAbstract {
|
|||||||
$item['title'] = htmlspecialchars($manga->nodeValue);
|
$item['title'] = htmlspecialchars($manga->nodeValue);
|
||||||
|
|
||||||
// Add each chapter to the feed
|
// Add each chapter to the feed
|
||||||
$item['content'] = "";
|
$item['content'] = '';
|
||||||
|
|
||||||
foreach ($chapters as $chapter) {
|
foreach ($chapters as $chapter) {
|
||||||
if($item['content'] <> "") {
|
if($item['content'] <> '') {
|
||||||
$item['content'] .= "<br>";
|
$item['content'] .= '<br>';
|
||||||
}
|
}
|
||||||
$item['content'] .= "<a href='"
|
$item['content'] .= "<a href='"
|
||||||
. self::URI
|
. self::URI
|
||||||
. htmlspecialchars($chapter->getAttribute('href'))
|
. htmlspecialchars($chapter->getAttribute('href'))
|
||||||
. "'>"
|
. "'>"
|
||||||
. htmlspecialchars($chapter->nodeValue)
|
. htmlspecialchars($chapter->nodeValue)
|
||||||
. "</a>";
|
. '</a>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
@@ -211,13 +211,13 @@ EOD;
|
|||||||
|
|
||||||
foreach ($chapters as $chapter) {
|
foreach ($chapters as $chapter) {
|
||||||
$item = array();
|
$item = array();
|
||||||
$item['title'] = htmlspecialchars($xpath->query("td[1]", $chapter)
|
$item['title'] = htmlspecialchars($xpath->query('td[1]', $chapter)
|
||||||
->item(0)
|
->item(0)
|
||||||
->nodeValue);
|
->nodeValue);
|
||||||
$item['uri'] = self::URI . $xpath->query("td[1]/a", $chapter)
|
$item['uri'] = self::URI . $xpath->query('td[1]/a', $chapter)
|
||||||
->item(0)
|
->item(0)
|
||||||
->getAttribute('href');
|
->getAttribute('href');
|
||||||
$item['timestamp'] = strtotime($xpath->query("td[2]", $chapter)
|
$item['timestamp'] = strtotime($xpath->query('td[2]', $chapter)
|
||||||
->item(0)
|
->item(0)
|
||||||
->nodeValue);
|
->nodeValue);
|
||||||
array_unshift($this->items, $item);
|
array_unshift($this->items, $item);
|
||||||
@@ -227,12 +227,12 @@ EOD;
|
|||||||
public function getURI(){
|
public function getURI(){
|
||||||
switch($this->queriedContext) {
|
switch($this->queriedContext) {
|
||||||
case 'Get latest updates':
|
case 'Get latest updates':
|
||||||
$path = "latest";
|
$path = 'latest';
|
||||||
break;
|
break;
|
||||||
case 'Get popular mangas':
|
case 'Get popular mangas':
|
||||||
$path = "popular";
|
$path = 'popular';
|
||||||
if($this->getInput('category') !== "all") {
|
if($this->getInput('category') !== 'all') {
|
||||||
$path .= "/" . $this->getInput('category');
|
$path .= '/' . $this->getInput('category');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'Get manga updates':
|
case 'Get manga updates':
|
||||||
|
144
bridges/MydealsBridge.php
Normal file
144
bridges/MydealsBridge.php
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/DealabsBridge.php');
|
||||||
|
class MydealsBridge extends PepperBridgeAbstract {
|
||||||
|
|
||||||
|
const NAME = 'Mydeals bridge';
|
||||||
|
const URI = 'https://www.mydealz.de/';
|
||||||
|
const DESCRIPTION = 'Zeigt die Deals von mydeals.de';
|
||||||
|
const MAINTAINER = 'sysadminstory';
|
||||||
|
const PARAMETERS = array(
|
||||||
|
'Suche nach Stichworten' => array (
|
||||||
|
'q' => array(
|
||||||
|
'name' => 'Stichworten',
|
||||||
|
'type' => 'text',
|
||||||
|
'required' => true
|
||||||
|
),
|
||||||
|
'hide_expired' => array(
|
||||||
|
'name' => 'Abgelaufenes ausblenden',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'required' => 'true'
|
||||||
|
),
|
||||||
|
'hide_local' => array(
|
||||||
|
'name' => 'Lokales ausblenden',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'title' => 'Deals im physischen Geschäft ausblenden',
|
||||||
|
'required' => 'true'
|
||||||
|
),
|
||||||
|
'priceFrom' => array(
|
||||||
|
'name' => 'Minimaler Preis',
|
||||||
|
'type' => 'text',
|
||||||
|
'title' => 'Minmaler Preis in Euros',
|
||||||
|
'required' => 'false',
|
||||||
|
'defaultValue' => ''
|
||||||
|
),
|
||||||
|
'priceTo' => array(
|
||||||
|
'name' => 'Maximaler Preis',
|
||||||
|
'type' => 'text',
|
||||||
|
'title' => 'maximaler Preis in Euro',
|
||||||
|
'required' => 'false',
|
||||||
|
'defaultValue' => ''
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
'Deals pro Gruppen' => array(
|
||||||
|
'group' => array(
|
||||||
|
'name' => 'Gruppen',
|
||||||
|
'type' => 'list',
|
||||||
|
'required' => 'true',
|
||||||
|
'title' => 'Gruppe, deren Deals angezeigt werden müssen',
|
||||||
|
'values' => array(
|
||||||
|
'Elektronik' => 'elektronik',
|
||||||
|
'Handy & Smartphone' => 'smartphone',
|
||||||
|
'Gaming' => 'gaming',
|
||||||
|
'Software' => 'apps-software',
|
||||||
|
'Fashion Frauen' => 'fashion-frauen',
|
||||||
|
'Fashion Männer' => 'fashion-accessoires',
|
||||||
|
'Beauty & Gesundheit' => 'beauty',
|
||||||
|
'Family & Kids' => 'family-kids',
|
||||||
|
'Essen & Trinken' => 'food',
|
||||||
|
'Freizeit & Reisen' => 'reisen',
|
||||||
|
'Haushalt & Garten' => 'home-living',
|
||||||
|
'Entertainment' => 'entertainment',
|
||||||
|
'Verträge & Finanzen' => 'vertraege-finanzen',
|
||||||
|
'Coupons' => 'coupons',
|
||||||
|
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'order' => array(
|
||||||
|
'name' => 'sortieren nach',
|
||||||
|
'type' => 'list',
|
||||||
|
'required' => 'true',
|
||||||
|
'title' => 'Sortierung der deals',
|
||||||
|
'values' => array(
|
||||||
|
'Vom heißesten zum kältesten Deal' => '',
|
||||||
|
'Vom jüngsten Deal zum ältesten' => '-new',
|
||||||
|
'Vom am meisten kommentierten Deal zum am wenigsten kommentierten Deal' => '-discussed'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
public $lang = array(
|
||||||
|
'bridge-uri' => SELF::URI,
|
||||||
|
'bridge-name' => SELF::NAME,
|
||||||
|
'context-keyword' => 'Suche nach Stichworten',
|
||||||
|
'context-group' => 'Deals pro Gruppen',
|
||||||
|
'uri-group' => '/gruppe/',
|
||||||
|
'request-error' => 'Could not request mydeals',
|
||||||
|
'no-results' => 'Ups, wir konnten keine Deals zu',
|
||||||
|
'relative-date-indicator' => array(
|
||||||
|
'vor',
|
||||||
|
'seit'
|
||||||
|
),
|
||||||
|
'price' => 'Preis',
|
||||||
|
'shipping' => 'Versand',
|
||||||
|
'origin' => 'Ursprung',
|
||||||
|
'discount' => 'Rabatte',
|
||||||
|
'title-keyword' => 'Suche',
|
||||||
|
'title-group' => 'Gruppe',
|
||||||
|
'local-months' => array(
|
||||||
|
'Jan',
|
||||||
|
'Feb',
|
||||||
|
'Mär',
|
||||||
|
'Apr',
|
||||||
|
'Mai',
|
||||||
|
'Jun',
|
||||||
|
'Jul',
|
||||||
|
'Aug',
|
||||||
|
'Sep',
|
||||||
|
'Okt',
|
||||||
|
'Nov',
|
||||||
|
'Dez',
|
||||||
|
'.'
|
||||||
|
),
|
||||||
|
'local-time-relative' => array(
|
||||||
|
'eingestellt vor ',
|
||||||
|
'm',
|
||||||
|
'h,',
|
||||||
|
'day',
|
||||||
|
'days',
|
||||||
|
'month',
|
||||||
|
'year',
|
||||||
|
'and '
|
||||||
|
),
|
||||||
|
'date-prefixes' => array(
|
||||||
|
'eingestellt am ',
|
||||||
|
'lokal ',
|
||||||
|
'aktualisiert ',
|
||||||
|
),
|
||||||
|
'relative-date-alt-prefixes' => array(
|
||||||
|
'aktualisiert vor ',
|
||||||
|
'kommentiert vor ',
|
||||||
|
'heiß seit '
|
||||||
|
),
|
||||||
|
'relative-date-ignore-suffix' => array(
|
||||||
|
'/von.*$/'
|
||||||
|
),
|
||||||
|
'localdeal' => array(
|
||||||
|
'Lokal ',
|
||||||
|
'Läuft bis '
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
@@ -12,7 +12,7 @@ class NasaApodBridge extends BridgeAbstract {
|
|||||||
$html = getSimpleHTMLDOM(self::URI . 'archivepix.html')
|
$html = getSimpleHTMLDOM(self::URI . 'archivepix.html')
|
||||||
or returnServerError('Error while downloading the website content');
|
or returnServerError('Error while downloading the website content');
|
||||||
|
|
||||||
$list = explode("<br>", $html->find('b', 0)->innertext);
|
$list = explode('<br>', $html->find('b', 0)->innertext);
|
||||||
|
|
||||||
for($i = 0; $i < 3; $i++) {
|
for($i = 0; $i < 3; $i++) {
|
||||||
$line = $list[$i];
|
$line = $list[$i];
|
||||||
@@ -32,7 +32,7 @@ class NasaApodBridge extends BridgeAbstract {
|
|||||||
$explanation = $picture_html->find('p', 2)->innertext;
|
$explanation = $picture_html->find('p', 2)->innertext;
|
||||||
|
|
||||||
//Extract date from the picture page
|
//Extract date from the picture page
|
||||||
$date = explode(" ", $picture_html->find('p', 1)->innertext);
|
$date = explode(' ', $picture_html->find('p', 1)->innertext);
|
||||||
$item['timestamp'] = strtotime($date[4] . $date[3] . $date[2]);
|
$item['timestamp'] = strtotime($date[4] . $date[3] . $date[2]);
|
||||||
|
|
||||||
//Other informations
|
//Other informations
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@@ -44,7 +44,7 @@ class PinterestBridge extends FeedExpander {
|
|||||||
$pattern = '/https\:\/\/i\.pinimg\.com\/[a-zA-Z0-9]*x\//';
|
$pattern = '/https\:\/\/i\.pinimg\.com\/[a-zA-Z0-9]*x\//';
|
||||||
foreach($this->items as $item) {
|
foreach($this->items as $item) {
|
||||||
|
|
||||||
$item["content"] = preg_replace($pattern, 'https://i.pinimg.com/originals/', $item["content"]);
|
$item['content'] = preg_replace($pattern, 'https://i.pinimg.com/originals/', $item['content']);
|
||||||
$newitems[] = $item;
|
$newitems[] = $item;
|
||||||
}
|
}
|
||||||
$this->items = $newitems;
|
$this->items = $newitems;
|
||||||
@@ -64,10 +64,10 @@ class PinterestBridge extends FeedExpander {
|
|||||||
// provide even less info. Thus we attempt multiple options.
|
// provide even less info. Thus we attempt multiple options.
|
||||||
$item['title'] = trim($result['title']);
|
$item['title'] = trim($result['title']);
|
||||||
|
|
||||||
if($item['title'] === "")
|
if($item['title'] === '')
|
||||||
$item['title'] = trim($result['rich_summary']['display_name']);
|
$item['title'] = trim($result['rich_summary']['display_name']);
|
||||||
|
|
||||||
if($item['title'] === "")
|
if($item['title'] === '')
|
||||||
$item['title'] = trim($result['grid_description']);
|
$item['title'] = trim($result['grid_description']);
|
||||||
|
|
||||||
$item['timestamp'] = strtotime($result['created_at']);
|
$item['timestamp'] = strtotime($result['created_at']);
|
||||||
|
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';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -8,9 +8,9 @@ class RainbowSixSiegeBridge extends BridgeAbstract {
|
|||||||
const DESCRIPTION = 'Latest articles from the Rainbow Six Siege blog';
|
const DESCRIPTION = 'Latest articles from the Rainbow Six Siege blog';
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
$dlUrl = "https://prod-tridionservice.ubisoft.com/live/v1/News/Latest?templateId=tcm%3A152-7677";
|
$dlUrl = 'https://prod-tridionservice.ubisoft.com/live/v1/News/Latest?templateId=tcm%3A152-7677';
|
||||||
$dlUrl .= "8-32&pageIndex=0&pageSize=10&language=en-US&detailPageId=tcm%3A152-194572-64";
|
$dlUrl .= '8-32&pageIndex=0&pageSize=10&language=en-US&detailPageId=tcm%3A152-194572-64';
|
||||||
$dlUrl .= "&keywordList=175426&siteId=undefined&useSeoFriendlyUrl=true";
|
$dlUrl .= '&keywordList=175426&siteId=undefined&useSeoFriendlyUrl=true';
|
||||||
$jsonString = getContents($dlUrl) or returnServerError('Error while downloading the website content');
|
$jsonString = getContents($dlUrl) or returnServerError('Error while downloading the website content');
|
||||||
|
|
||||||
$json = json_decode($jsonString, true);
|
$json = json_decode($jsonString, true);
|
||||||
|
@@ -25,7 +25,7 @@ class ReadComicsBridge extends BridgeAbstract {
|
|||||||
return $timestamp;
|
return $timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
$keywordsList = explode(";", $this->getInput('q'));
|
$keywordsList = explode(';', $this->getInput('q'));
|
||||||
foreach($keywordsList as $keywords) {
|
foreach($keywordsList as $keywords) {
|
||||||
$html = $this->getSimpleHTMLDOM(self::URI . 'comic/' . rawurlencode($keywords))
|
$html = $this->getSimpleHTMLDOM(self::URI . 'comic/' . rawurlencode($keywords))
|
||||||
or $this->returnServerError('Could not request readcomics.tv.');
|
or $this->returnServerError('Could not request readcomics.tv.');
|
||||||
|
@@ -19,7 +19,7 @@ class ReporterreBridge extends BridgeAbstract {
|
|||||||
// Replace all relative urls with absolute ones
|
// Replace all relative urls with absolute ones
|
||||||
$text = preg_replace(
|
$text = preg_replace(
|
||||||
'/(href|src)(\=[\"\'])(?!http)([^"\']+)/ims',
|
'/(href|src)(\=[\"\'])(?!http)([^"\']+)/ims',
|
||||||
"$1$2" . self::URI . "$3",
|
'$1$2' . self::URI . '$3',
|
||||||
$text
|
$text
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -9,9 +9,9 @@ class Rue89Bridge extends FeedExpander {
|
|||||||
protected function parseItem($item){
|
protected function parseItem($item){
|
||||||
$item = parent::parseItem($item);
|
$item = parent::parseItem($item);
|
||||||
|
|
||||||
$url = "http://api.rue89.nouvelobs.com/export/mobile2/node/"
|
$url = 'http://api.rue89.nouvelobs.com/export/mobile2/node/'
|
||||||
. str_replace(" ", "", substr($item['uri'], -8))
|
. str_replace(' ', '', substr($item['uri'], -8))
|
||||||
. "/full";
|
. '/full';
|
||||||
|
|
||||||
$datas = json_decode(getContents($url), true);
|
$datas = json_decode(getContents($url), true);
|
||||||
$item['content'] = $datas['node']['body'];
|
$item['content'] = $datas['node']['body'];
|
||||||
|
@@ -32,7 +32,7 @@ class SexactuBridge extends BridgeAbstract {
|
|||||||
$item = array();
|
$item = array();
|
||||||
$item['author'] = self::AUTHOR;
|
$item['author'] = self::AUTHOR;
|
||||||
$item['title'] = $title->plaintext;
|
$item['title'] = $title->plaintext;
|
||||||
$urlAttribute = "data-href";
|
$urlAttribute = 'data-href';
|
||||||
$uri = $title->$urlAttribute;
|
$uri = $title->$urlAttribute;
|
||||||
if($uri === false)
|
if($uri === false)
|
||||||
continue;
|
continue;
|
||||||
|
@@ -73,7 +73,7 @@ class ShanaprojectBridge extends BridgeAbstract {
|
|||||||
// Getting the picture is a little bit tricky as it is part of the style.
|
// Getting the picture is a little bit tricky as it is part of the style.
|
||||||
// Luckily the style is part of the parent div :)
|
// Luckily the style is part of the parent div :)
|
||||||
|
|
||||||
if(preg_match("/url\(\/\/([^\)]+)\)/i", $anime->parent->style, $matches))
|
if(preg_match('/url\(\/\/([^\)]+)\)/i', $anime->parent->style, $matches))
|
||||||
return $matches[1];
|
return $matches[1];
|
||||||
|
|
||||||
returnServerError('Could not extract background image!');
|
returnServerError('Could not extract background image!');
|
||||||
|
@@ -21,7 +21,7 @@ class Shimmie2Bridge extends DanbooruBridge {
|
|||||||
protected function getItemFromElement($element){
|
protected function getItemFromElement($element){
|
||||||
$item = array();
|
$item = array();
|
||||||
$item['uri'] = $this->getURI() . $element->href;
|
$item['uri'] = $this->getURI() . $element->href;
|
||||||
$item['id'] = (int)preg_replace("/[^0-9]/", '', $element->getAttribute(static::IDATTRIBUTE));
|
$item['id'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE));
|
||||||
$item['timestamp'] = time();
|
$item['timestamp'] = time();
|
||||||
$thumbnailUri = $this->getURI() . $element->find('img', 0)->src;
|
$thumbnailUri = $this->getURI() . $element->find('img', 0)->src;
|
||||||
$item['tags'] = $element->getAttribute('data-tags');
|
$item['tags'] = $element->getAttribute('data-tags');
|
||||||
|
825
bridges/SkimfeedBridge.php
Normal file
825
bridges/SkimfeedBridge.php
Normal file
@@ -0,0 +1,825 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class SkimfeedBridge extends BridgeAbstract {
|
||||||
|
|
||||||
|
const CONTEXT_NEWS_BOX = 'News box';
|
||||||
|
const CONTEXT_HOT_TOPICS = 'Hot topics';
|
||||||
|
const CONTEXT_TECH_NEWS = 'Tech news';
|
||||||
|
const CONTEXT_CUSTOM = 'Custom feed';
|
||||||
|
|
||||||
|
const NAME = 'Skimfeed Bridge';
|
||||||
|
const URI = 'https://skimfeed.com';
|
||||||
|
const DESCRIPTION = 'Returns feeds from Skimfeed, also supports custom feeds!';
|
||||||
|
const MAINTAINER = 'logmanoriginal';
|
||||||
|
const CACHE_TIMEOUT = 3600;
|
||||||
|
|
||||||
|
const PARAMETERS = array(
|
||||||
|
self::CONTEXT_NEWS_BOX => array( // auto-generated (see below)
|
||||||
|
'box_channel' => array(
|
||||||
|
'name' => 'Channel',
|
||||||
|
'type' => 'list',
|
||||||
|
'required' => true,
|
||||||
|
'title' => 'Select your channel',
|
||||||
|
'values' => array(
|
||||||
|
'Hacker News' => '/news/hacker-news.html',
|
||||||
|
'QZ' => '/news/qz.html',
|
||||||
|
'The Verge' => '/news/the-verge.html',
|
||||||
|
'Slashdot' => '/news/slashdot.html',
|
||||||
|
'Lifehacker' => '/news/lifehacker.html',
|
||||||
|
'Gizmag' => '/news/gizmag.html',
|
||||||
|
'Fast Company' => '/news/fast-company.html',
|
||||||
|
'Engadget' => '/news/engadget.html',
|
||||||
|
'Wired' => '/news/wired.html',
|
||||||
|
'MakeUseOf' => '/news/makeuseof.html',
|
||||||
|
'Techcrunch' => '/news/techcrunch.html',
|
||||||
|
'Apple Insider' => '/news/apple-insider.html',
|
||||||
|
'ArsTechnica' => '/news/arstechnica.html',
|
||||||
|
'Tech in Asia' => '/news/tech-in-asia.html',
|
||||||
|
'FastCoExist' => '/news/fastcoexist.html',
|
||||||
|
'Digital Trends' => '/news/digital-trends.html',
|
||||||
|
'AnandTech' => '/news/anandtech.html',
|
||||||
|
'How to Geek' => '/news/how-to-geek.html',
|
||||||
|
'Geek' => '/news/geek.html',
|
||||||
|
'BBC Technology' => '/news/bbc-technology.html',
|
||||||
|
'Extreme Tech' => '/news/extreme-tech.html',
|
||||||
|
'Packet Storm Sec' => '/news/packet-storm-sec.html',
|
||||||
|
'MedGadget' => '/news/medgadget.html',
|
||||||
|
'Design' => '/news/design.html',
|
||||||
|
'The Next Web' => '/news/the-next-web.html',
|
||||||
|
'Bit-Tech' => '/news/bit-tech.html',
|
||||||
|
'Next Big Future' => '/news/next-big-future.html',
|
||||||
|
'A VC' => '/news/a-vc.html',
|
||||||
|
'Copyblogger' => '/news/copyblogger.html',
|
||||||
|
'Smashing Mag' => '/news/smashing-mag.html',
|
||||||
|
'Continuations' => '/news/continuations.html',
|
||||||
|
'Cult of Mac' => '/news/cult-of-mac.html',
|
||||||
|
'SecuriTeam' => '/news/securiteam.html',
|
||||||
|
'The Tech Block' => '/news/the-tech-block.html',
|
||||||
|
'BetaBeat' => '/news/betabeat.html',
|
||||||
|
'PC Mag' => '/news/pc-mag.html',
|
||||||
|
'Venture Beat' => '/news/venture-beat.html',
|
||||||
|
'ReadWriteWeb' => '/news/readwriteweb.html',
|
||||||
|
'High Scalability' => '/news/high-scalability.html',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
self::CONTEXT_HOT_TOPICS => array(),
|
||||||
|
self::CONTEXT_TECH_NEWS => array( // auto-generated (see below)
|
||||||
|
'tech_channel' => array(
|
||||||
|
'name' => 'Tech channel',
|
||||||
|
'type' => 'list',
|
||||||
|
'required' => true,
|
||||||
|
'title' => 'Select your tech channel',
|
||||||
|
'values' => array(
|
||||||
|
'Agg' => array(
|
||||||
|
'Reddit' => '/news/reddit.html',
|
||||||
|
'Tech Insider' => '/news/tech-insider.html',
|
||||||
|
'Digg' => '/news/digg.html',
|
||||||
|
'Meta Filter' => '/news/meta-filter.html',
|
||||||
|
'Fark' => '/news/fark.html',
|
||||||
|
'Mashable' => '/news/mashable.html',
|
||||||
|
'Ad Week' => '/news/ad-week.html',
|
||||||
|
'The Chive' => '/news/the-chive.html',
|
||||||
|
'BoingBoing' => '/news/boingboing.html',
|
||||||
|
'Vice' => '/news/vice.html',
|
||||||
|
'ClientsFromHell' => '/news/clientsfromhell.html',
|
||||||
|
'How Stuff Works' => '/news/how-stuff-works.html',
|
||||||
|
'Buzzfeed' => '/news/buzzfeed.html',
|
||||||
|
'BoingBoing' => '/news/boingboing.html',
|
||||||
|
'Cracked' => '/news/cracked.html',
|
||||||
|
'Weird News' => '/news/weird-news.html',
|
||||||
|
'ITOTD' => '/news/itotd.html',
|
||||||
|
'Metafilter' => '/news/metafilter.html',
|
||||||
|
'TheOnion' => '/news/theonion.html',
|
||||||
|
),
|
||||||
|
'Cars' => array(
|
||||||
|
'Reddit Cars' => '/news/reddit-cars.html',
|
||||||
|
'NYT Auto' => '/news/nyt-auto.html',
|
||||||
|
'Truth About Cars' => '/news/truth-about-cars.html',
|
||||||
|
'AutoBlog' => '/news/autoblog.html',
|
||||||
|
'AutoSpies' => '/news/autospies.html',
|
||||||
|
'Autoweek' => '/news/autoweek.html',
|
||||||
|
'The Garage' => '/news/the-garage.html',
|
||||||
|
'Car and Driver' => '/news/car-and-driver.html',
|
||||||
|
'EGM Car Tech' => '/news/egm-car-tech.html',
|
||||||
|
'Top Gear' => '/news/top-gear.html',
|
||||||
|
'eGarage' => '/news/egarage.html',
|
||||||
|
),
|
||||||
|
'Comics' => array(
|
||||||
|
'Penny Arcade' => '/news/penny-arcade.html',
|
||||||
|
'XKCD' => '/news/xkcd.html',
|
||||||
|
'Channelate' => '/news/channelate.html',
|
||||||
|
'Savage Chicken' => '/news/savage-chicken.html',
|
||||||
|
'Dinosaur Comics' => '/news/dinosaur-comics.html',
|
||||||
|
'Explosm' => '/news/explosm.html',
|
||||||
|
'PoorlyDLines' => '/news/poorlydlines.html',
|
||||||
|
'Moonbeard' => '/news/moonbeard.html',
|
||||||
|
'Nedroid' => '/news/nedroid.html',
|
||||||
|
),
|
||||||
|
'Design' => array(
|
||||||
|
'FastCoCreate' => '/news/fastcocreate.html',
|
||||||
|
'Dezeen' => '/news/dezeen.html',
|
||||||
|
'Design Boom' => '/news/design-boom.html',
|
||||||
|
'Mmminimal' => '/news/mmminimal.html',
|
||||||
|
'We Heart' => '/news/we-heart.html',
|
||||||
|
'CreativeBloq' => '/news/creativebloq.html',
|
||||||
|
'TheDSGNblog' => '/news/thedsgnblog.html',
|
||||||
|
'Grainedit' => '/news/grainedit.html',
|
||||||
|
),
|
||||||
|
'Football' => array(
|
||||||
|
'Mail Football' => '/news/mail-football.html',
|
||||||
|
'Yahoo Football' => '/news/yahoo-football.html',
|
||||||
|
'FourFourTwo' => '/news/fourfourtwo.html',
|
||||||
|
'Goal' => '/news/goal.html',
|
||||||
|
'BBC Football' => '/news/bbc-football.html',
|
||||||
|
'TalkSport' => '/news/talksport.html',
|
||||||
|
'101 Great Goals' => '/news/101-great-goals.html',
|
||||||
|
'Who Scored' => '/news/who-scored.html',
|
||||||
|
'Football365 Champ' => '/news/football365-champ.html',
|
||||||
|
'Football365 Premier' => '/news/football365-premier.html',
|
||||||
|
'BleacherReport' => '/news/bleacherreport.html',
|
||||||
|
),
|
||||||
|
'Gaming' => array(
|
||||||
|
'Polygon' => '/news/polygon.html',
|
||||||
|
'Gamespot' => '/news/gamespot.html',
|
||||||
|
'RockPaperShotgun' => '/news/rockpapershotgun.html',
|
||||||
|
'VG247' => '/news/vg247.html',
|
||||||
|
'IGN' => '/news/ign.html',
|
||||||
|
'Reddit Games' => '/news/reddit-games.html',
|
||||||
|
'TouchArcade' => '/news/toucharcade.html',
|
||||||
|
'GamesRadar' => '/news/gamesradar.html',
|
||||||
|
'Siliconera' => '/news/siliconera.html',
|
||||||
|
'Reddit GameDeals' => '/news/reddit-gamedeals.html',
|
||||||
|
'Joystiq' => '/news/joystiq.html',
|
||||||
|
'GameInformer' => '/news/gameinformer.html',
|
||||||
|
'PSN Blog' => '/news/psn-blog.html',
|
||||||
|
'Reddit GamerNews' => '/news/reddit-gamernews.html',
|
||||||
|
'Steam' => '/news/steam.html',
|
||||||
|
'DualShockers' => '/news/dualshockers.html',
|
||||||
|
'ShackNews' => '/news/shacknews.html',
|
||||||
|
'CheapAssGamer' => '/news/cheapassgamer.html',
|
||||||
|
'Eurogamer' => '/news/eurogamer.html',
|
||||||
|
'Major Nelson' => '/news/major-nelson.html',
|
||||||
|
'Reddit Truegaming' => '/news/reddit-truegaming.html',
|
||||||
|
'GameTrailers' => '/news/gametrailers.html',
|
||||||
|
'GamaSutra' => '/news/gamasutra.html',
|
||||||
|
'USGamer' => '/news/usgamer.html',
|
||||||
|
'Shoryuken' => '/news/shoryuken.html',
|
||||||
|
'Destructoid' => '/news/destructoid.html',
|
||||||
|
'ArsGaming' => '/news/arsgaming.html',
|
||||||
|
'XBOX Blog' => '/news/xbox-blog.html',
|
||||||
|
'GiantBomb' => '/news/giantbomb.html',
|
||||||
|
'VideoGamer' => '/news/videogamer.html',
|
||||||
|
'Pocket Tactics' => '/news/pocket-tactics.html',
|
||||||
|
'WiredGaming' => '/news/wiredgaming.html',
|
||||||
|
'AllGamesBeta' => '/news/allgamesbeta.html',
|
||||||
|
'OnGamers' => '/news/ongamers.html',
|
||||||
|
'Reddit GameBundles' => '/news/reddit-gamebundles.html',
|
||||||
|
'Kotaku' => '/news/kotaku.html',
|
||||||
|
'PCGamer' => '/news/pcgamer.html',
|
||||||
|
),
|
||||||
|
'Investing' => array(
|
||||||
|
'Seeking Alpha' => '/news/seeking-alpha.html',
|
||||||
|
'BBC Business' => '/news/bbc-business.html',
|
||||||
|
'Harvard Biz' => '/news/harvard-biz.html',
|
||||||
|
'Market Watch' => '/news/market-watch.html',
|
||||||
|
'Investor Place' => '/news/investor-place.html',
|
||||||
|
'Money Week' => '/news/money-week.html',
|
||||||
|
'Moneybeat' => '/news/moneybeat.html',
|
||||||
|
'Dealbook' => '/news/dealbook.html',
|
||||||
|
'Economist Business' => '/news/economist-business.html',
|
||||||
|
'Economist' => '/news/economist.html',
|
||||||
|
'Economist CN' => '/news/economist-cn.html',
|
||||||
|
),
|
||||||
|
'Long' => array(
|
||||||
|
'The Atlantic' => '/news/the-atlantic.html',
|
||||||
|
'Reddit Long' => '/news/reddit-long.html',
|
||||||
|
'Paris Review' => '/news/paris-review.html',
|
||||||
|
'New Yorker' => '/news/new-yorker.html',
|
||||||
|
'LongForm' => '/news/longform.html',
|
||||||
|
'LongReads' => '/news/longreads.html',
|
||||||
|
'The Browser' => '/news/the-browser.html',
|
||||||
|
'The Feature' => '/news/the-feature.html',
|
||||||
|
),
|
||||||
|
'MMA' => array(
|
||||||
|
'MMA Weekly' => '/news/mma-weekly.html',
|
||||||
|
'MMAFighting' => '/news/mmafighting.html',
|
||||||
|
'Reddit MMA' => '/news/reddit-mma.html',
|
||||||
|
'Sherdog Articles' => '/news/sherdog-articles.html',
|
||||||
|
'FightLand Vice' => '/news/fightland-vice.html',
|
||||||
|
'Sherdog Forum' => '/news/sherdog-forum.html',
|
||||||
|
'MMA Junkie' => '/news/mma-junkie.html',
|
||||||
|
'Sherdog MMA Video' => '/news/sherdog-mma-video.html',
|
||||||
|
'BloodyElbow' => '/news/bloodyelbow.html',
|
||||||
|
'CageWriter' => '/news/cagewriter.html',
|
||||||
|
'Sherdog News' => '/news/sherdog-news.html',
|
||||||
|
'MMAForum' => '/news/mmaforum.html',
|
||||||
|
'MMA Junkie Radio' => '/news/mma-junkie-radio.html',
|
||||||
|
'UFC News' => '/news/ufc-news.html',
|
||||||
|
'FightLinker' => '/news/fightlinker.html',
|
||||||
|
'Bodybuilding MMA' => '/news/bodybuilding-mma.html',
|
||||||
|
'BleacherReport MMA' => '/news/bleacherreport-mma.html',
|
||||||
|
'FiveOuncesofPain' => '/news/fiveouncesofpain.html',
|
||||||
|
'Sherdog Pictures' => '/news/sherdog-pictures.html',
|
||||||
|
'CagePotato' => '/news/cagepotato.html',
|
||||||
|
'Sherdog Radio' => '/news/sherdog-radio.html',
|
||||||
|
'ProMMARadio' => '/news/prommaradio.html',
|
||||||
|
),
|
||||||
|
'Mobile' => array(
|
||||||
|
'Macrumors' => '/news/macrumors.html',
|
||||||
|
'Android Police' => '/news/android-police.html',
|
||||||
|
'GSM Arena' => '/news/gsm-arena.html',
|
||||||
|
'DigiTrend Mobile' => '/news/digitrend-mobile.html',
|
||||||
|
'Mobile Nation' => '/news/mobile-nation.html',
|
||||||
|
'TechRadar' => '/news/techradar.html',
|
||||||
|
'ZDNET Mobile' => '/news/zdnet-mobile.html',
|
||||||
|
'MacWorld' => '/news/macworld.html',
|
||||||
|
'Android Dev Blog' => '/news/android-dev-blog.html',
|
||||||
|
),
|
||||||
|
'News' => array(
|
||||||
|
'Daily Mail' => '/news/daily-mail.html',
|
||||||
|
'Business Insider' => '/news/business-insider.html',
|
||||||
|
'The Guardian' => '/news/the-guardian.html',
|
||||||
|
'Fox' => '/news/fox.html',
|
||||||
|
'BBC World' => '/news/bbc-world.html',
|
||||||
|
'MSNBC' => '/news/msnbc.html',
|
||||||
|
'ABC News' => '/news/abc-news.html',
|
||||||
|
'Al Jazeera' => '/news/al-jazeera.html',
|
||||||
|
'Business Insider India' => '/news/business-insider-india.html',
|
||||||
|
'Observer' => '/news/observer.html',
|
||||||
|
'NYT Tech' => '/news/nyt-tech.html',
|
||||||
|
'NYT World' => '/news/nyt-world.html',
|
||||||
|
'CNN' => '/news/cnn.html',
|
||||||
|
'Japan Times' => '/news/japan-times.html',
|
||||||
|
'WorldCrunch' => '/news/worldcrunch.html',
|
||||||
|
'Pro publica' => '/news/pro-publica.html',
|
||||||
|
'OZY' => '/news/ozy.html',
|
||||||
|
'Times of India' => '/news/times-of-india.html',
|
||||||
|
'The Australian' => '/news/the-australian.html',
|
||||||
|
'Harpers' => '/news/harpers.html',
|
||||||
|
'Moscow Times' => '/news/moscow-times.html',
|
||||||
|
'The Times' => '/news/the-times.html',
|
||||||
|
'Reuters Tech' => '/news/reuters-tech.html',
|
||||||
|
),
|
||||||
|
'Politics' => array(
|
||||||
|
'FreeRepublic' => '/news/freerepublic.html',
|
||||||
|
'Salon' => '/news/salon.html',
|
||||||
|
'DrudgeReport' => '/news/drudgereport.html',
|
||||||
|
'TheHill' => '/news/thehill.html',
|
||||||
|
'TheBlaze' => '/news/theblaze.html',
|
||||||
|
'InfoWars' => '/news/infowars.html',
|
||||||
|
'New Republic' => '/news/new-republic.html',
|
||||||
|
'WashTimes' => '/news/washtimes.html',
|
||||||
|
'RealCleanPol' => '/news/realcleanpol.html',
|
||||||
|
'Fact Check' => '/news/fact-check.html',
|
||||||
|
'DailyKos' => '/news/dailykos.html',
|
||||||
|
'NewsMax' => '/news/newsmax.html',
|
||||||
|
'Politico' => '/news/politico.html',
|
||||||
|
'Michelle Malkin' => '/news/michelle-malkin.html',
|
||||||
|
),
|
||||||
|
'Reddit' => array(
|
||||||
|
'R Movies' => '/news/r-movies.html',
|
||||||
|
'R News' => '/news/r-news.html',
|
||||||
|
'Futurology' => '/news/futurology.html',
|
||||||
|
'R All' => '/news/r-all.html',
|
||||||
|
'R Music' => '/news/r-music.html',
|
||||||
|
'R Askscience' => '/news/r-askscience.html',
|
||||||
|
'R Technology' => '/news/r-technology.html',
|
||||||
|
'R Bestof' => '/news/r-bestof.html',
|
||||||
|
'R Askreddit' => '/news/r-askreddit.html',
|
||||||
|
'R Worldnews' => '/news/r-worldnews.html',
|
||||||
|
'R Explainlikeimfive' => '/news/r-explainlikeimfive.html',
|
||||||
|
'R Iama' => '/news/r-iama.html',
|
||||||
|
),
|
||||||
|
'Science' => array(
|
||||||
|
'PhysOrg' => '/news/physorg.html',
|
||||||
|
'Hack-a-day' => '/news/hack-a-day.html',
|
||||||
|
'Reddit Science' => '/news/reddit-science.html',
|
||||||
|
'Stats Blog' => '/news/stats-blog.html',
|
||||||
|
'Flowing Data' => '/news/flowing-data.html',
|
||||||
|
'Eureka Alert' => '/news/eureka-alert.html',
|
||||||
|
'Robotics BizRev' => '/news/robotics-bizrev.html',
|
||||||
|
'Planet big Data' => '/news/planet-big-data.html',
|
||||||
|
'Makezine' => '/news/makezine.html',
|
||||||
|
'MIT Tech' => '/news/mit-tech.html',
|
||||||
|
'R Bloggers' => '/news/r-bloggers.html',
|
||||||
|
'DataIsBeautiful' => '/news/dataisbeautiful.html',
|
||||||
|
'Ted Videos' => '/news/ted-videos.html',
|
||||||
|
'Advanced Science' => '/news/advanced-science.html',
|
||||||
|
'Robotiq' => '/news/robotiq.html',
|
||||||
|
'Science Daily' => '/news/science-daily.html',
|
||||||
|
'IEEE Robotics' => '/news/ieee-robotics.html',
|
||||||
|
'PSFK' => '/news/psfk.html',
|
||||||
|
'Discover Magazine' => '/news/discover-magazine.html',
|
||||||
|
'DataTau' => '/news/datatau.html',
|
||||||
|
'RoboHub' => '/news/robohub.html',
|
||||||
|
'Discovery' => '/news/discovery.html',
|
||||||
|
'Smart Data' => '/news/smart-data.html',
|
||||||
|
'Whats Big Data' => '/news/whats-big-data.html',
|
||||||
|
),
|
||||||
|
'Tech' => array(
|
||||||
|
'Hacker News' => '/news/hacker-news.html',
|
||||||
|
'The Verge' => '/news/the-verge.html',
|
||||||
|
'Lifehacker' => '/news/lifehacker.html',
|
||||||
|
'Fast Company' => '/news/fast-company.html',
|
||||||
|
'ArsTechnica' => '/news/arstechnica.html',
|
||||||
|
'MakeUseOf' => '/news/makeuseof.html',
|
||||||
|
'FastCoExist' => '/news/fastcoexist.html',
|
||||||
|
'How to Geek' => '/news/how-to-geek.html',
|
||||||
|
'The Next Web' => '/news/the-next-web.html',
|
||||||
|
'Engadget' => '/news/engadget.html',
|
||||||
|
'Gizmag' => '/news/gizmag.html',
|
||||||
|
'QZ' => '/news/qz.html',
|
||||||
|
'Wired' => '/news/wired.html',
|
||||||
|
'Techcrunch' => '/news/techcrunch.html',
|
||||||
|
'Slashdot' => '/news/slashdot.html',
|
||||||
|
'Extreme Tech' => '/news/extreme-tech.html',
|
||||||
|
'AnandTech' => '/news/anandtech.html',
|
||||||
|
'Digital Trends' => '/news/digital-trends.html',
|
||||||
|
'Next Big Future' => '/news/next-big-future.html',
|
||||||
|
'Apple Insider' => '/news/apple-insider.html',
|
||||||
|
'Geek' => '/news/geek.html',
|
||||||
|
'BBC Technology' => '/news/bbc-technology.html',
|
||||||
|
'Bit-Tech' => '/news/bit-tech.html',
|
||||||
|
'Packet Storm Sec' => '/news/packet-storm-sec.html',
|
||||||
|
'Design' => '/news/design.html',
|
||||||
|
'High Scalability' => '/news/high-scalability.html',
|
||||||
|
'Smashing Mag' => '/news/smashing-mag.html',
|
||||||
|
'The Tech Block' => '/news/the-tech-block.html',
|
||||||
|
'A VC' => '/news/a-vc.html',
|
||||||
|
'Tech in Asia' => '/news/tech-in-asia.html',
|
||||||
|
'ReadWriteWeb' => '/news/readwriteweb.html',
|
||||||
|
'PC Mag' => '/news/pc-mag.html',
|
||||||
|
'Continuations' => '/news/continuations.html',
|
||||||
|
'Copyblogger' => '/news/copyblogger.html',
|
||||||
|
'Cult of Mac' => '/news/cult-of-mac.html',
|
||||||
|
'BetaBeat' => '/news/betabeat.html',
|
||||||
|
'MedGadget' => '/news/medgadget.html',
|
||||||
|
'SecuriTeam' => '/news/securiteam.html',
|
||||||
|
'Venture Beat' => '/news/venture-beat.html',
|
||||||
|
),
|
||||||
|
'Trend' => array(
|
||||||
|
'Trend Hunter' => '/news/trend-hunter.html',
|
||||||
|
'ApartmentT' => '/news/apartmentt.html',
|
||||||
|
'GQ' => '/news/gq.html',
|
||||||
|
'Digital Trends' => '/news/digital-trends.html',
|
||||||
|
'Cool Hunting' => '/news/cool-hunting.html',
|
||||||
|
'FastCoDesign' => '/news/fastcodesign.html',
|
||||||
|
'TC Startups' => '/news/tc-startups.html',
|
||||||
|
'Killer Startups' => '/news/killer-startups.html',
|
||||||
|
'DigiInfo' => '/news/digiinfo.html',
|
||||||
|
'New Startups' => '/news/new-startups.html',
|
||||||
|
'DigiTrends' => '/news/digitrends.html',
|
||||||
|
),
|
||||||
|
'Watches' => array(
|
||||||
|
'Hodinkee' => '/news/hodinkee.html',
|
||||||
|
'Quill and Pad' => '/news/quill-and-pad.html',
|
||||||
|
'Monochrome' => '/news/monochrome.html',
|
||||||
|
'Deployant' => '/news/deployant.html',
|
||||||
|
'Watches by SJX' => '/news/watches-by-sjx.html',
|
||||||
|
'Fratello Watches' => '/news/fratello-watches.html',
|
||||||
|
'A Blog to Watch' => '/news/a-blog-to-watch.html',
|
||||||
|
'Wound for Life' => '/news/wound-for-life.html',
|
||||||
|
'Watch Paper' => '/news/watch-paper.html',
|
||||||
|
'Watch Report' => '/news/watch-report.html',
|
||||||
|
'Perpetuelle' => '/news/perpetuelle.html',
|
||||||
|
),
|
||||||
|
'Youtube' => array(
|
||||||
|
'LinusTechTips' => '/news/linustechtips.html',
|
||||||
|
'MetalJesusRocks' => '/news/metaljesusrocks.html',
|
||||||
|
'TotalBiscuit' => '/news/totalbiscuit.html',
|
||||||
|
'DexBonus' => '/news/dexbonus.html',
|
||||||
|
'Lon Siedman' => '/news/lon-siedman.html',
|
||||||
|
'MKBHD' => '/news/mkbhd.html',
|
||||||
|
'Terry A Davis' => '/news/terry-a-davis.html',
|
||||||
|
'HappyConsole' => '/news/happyconsole.html',
|
||||||
|
'Austin Evans' => '/news/austin-evans.html',
|
||||||
|
'NCIX' => '/news/ncix.html',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
self::CONTEXT_CUSTOM => array(
|
||||||
|
'config' => array(
|
||||||
|
'name' => 'Configuration',
|
||||||
|
'type' => 'text',
|
||||||
|
'required' => true,
|
||||||
|
'title' => 'Enter feed numbers from Skimfeed!',
|
||||||
|
'exampleValue' => '5,8,2,l,p,9,23'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'global' => array(
|
||||||
|
'limit' => array(
|
||||||
|
'name' => 'Limit',
|
||||||
|
'type' => 'number',
|
||||||
|
'title' => 'Limits the number of returned items in the feed',
|
||||||
|
'exampleValue' => 10
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
public function getURI() {
|
||||||
|
|
||||||
|
switch($this->queriedContext) {
|
||||||
|
|
||||||
|
case self::CONTEXT_NEWS_BOX:
|
||||||
|
|
||||||
|
$channel = $this->getInput('box_channel');
|
||||||
|
|
||||||
|
if($channel) {
|
||||||
|
return static::URI . $channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::CONTEXT_HOT_TOPICS:
|
||||||
|
return static::URI;
|
||||||
|
|
||||||
|
case self::CONTEXT_TECH_NEWS:
|
||||||
|
|
||||||
|
$channel = $this->getInput('tech_channel');
|
||||||
|
|
||||||
|
if($channel) {
|
||||||
|
return static::URI . $channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::CONTEXT_CUSTOM:
|
||||||
|
|
||||||
|
$config = $this->getInput('config');
|
||||||
|
|
||||||
|
return static::URI . '/custom.php?f=' . urlencode($config);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getURI();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName() {
|
||||||
|
|
||||||
|
switch($this->queriedContext) {
|
||||||
|
|
||||||
|
case self::CONTEXT_NEWS_BOX:
|
||||||
|
|
||||||
|
$channel = $this->getInput('box_channel');
|
||||||
|
|
||||||
|
$title = array_search(
|
||||||
|
$channel,
|
||||||
|
static::PARAMETERS[self::CONTEXT_NEWS_BOX]['box_channel']['values']
|
||||||
|
);
|
||||||
|
|
||||||
|
return $title . ' - ' . static::NAME;
|
||||||
|
|
||||||
|
case self::CONTEXT_HOT_TOPICS:
|
||||||
|
return 'Hot topics - ' . static::NAME;
|
||||||
|
|
||||||
|
case self::CONTEXT_TECH_NEWS:
|
||||||
|
|
||||||
|
$channel = $this->getInput('tech_channel');
|
||||||
|
|
||||||
|
$titles = array();
|
||||||
|
|
||||||
|
foreach(static::PARAMETERS[self::CONTEXT_TECH_NEWS]['tech_channel']['values'] as $ch) {
|
||||||
|
$titles = array_merge($titles, $ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = array_search($channel, $titles);
|
||||||
|
|
||||||
|
return $title . ' - ' . static::NAME;
|
||||||
|
|
||||||
|
case self::CONTEXT_CUSTOM:
|
||||||
|
return 'Custom - ' . static::NAME;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getName();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
|
||||||
|
// enable to export parameter lists
|
||||||
|
// $this->exportBoxChannels(); die;
|
||||||
|
// $this->exportTechChannels(); die;
|
||||||
|
|
||||||
|
$html = getSimpleHTMLDOM($this->getURI())
|
||||||
|
or returnServerError('Request to ' . $this->getURI() . ' failed!');
|
||||||
|
|
||||||
|
defaultLinkTo($html, static::URI);
|
||||||
|
|
||||||
|
switch($this->queriedContext) {
|
||||||
|
|
||||||
|
case self::CONTEXT_NEWS_BOX:
|
||||||
|
|
||||||
|
$author = array_search(
|
||||||
|
$this->getInput('box_channel'),
|
||||||
|
static::PARAMETERS[self::CONTEXT_NEWS_BOX]['box_channel']['values']
|
||||||
|
);
|
||||||
|
|
||||||
|
$author = '<a href="'
|
||||||
|
. $this->getURI()
|
||||||
|
. '">'
|
||||||
|
. $author
|
||||||
|
. '</a>';
|
||||||
|
|
||||||
|
$this->extractFeed($html, $author);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::CONTEXT_HOT_TOPICS:
|
||||||
|
$this->extractHotTopics($html);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::CONTEXT_TECH_NEWS:
|
||||||
|
$authors = array();
|
||||||
|
|
||||||
|
foreach(static::PARAMETERS[self::CONTEXT_TECH_NEWS]['tech_channel']['values'] as $ch) {
|
||||||
|
$authors = array_merge($authors, $ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
$author = '<a href="'
|
||||||
|
. $this->getURI()
|
||||||
|
. '">'
|
||||||
|
. array_search($this->getInput('tech_channel'), $authors)
|
||||||
|
. '</a>';
|
||||||
|
|
||||||
|
$this->extractFeed($html, $author);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::CONTEXT_CUSTOM:
|
||||||
|
$this->extractCustomFeed($html);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractFeed($html, $author) {
|
||||||
|
|
||||||
|
$articles = $html->find('li')
|
||||||
|
or returnServerError('Could not find articles!');
|
||||||
|
|
||||||
|
if(count($articles) === 1
|
||||||
|
&& stristr($articles[0]->plaintext, 'Nothing new in the last 48 hours')) {
|
||||||
|
return; // Nothing to show
|
||||||
|
}
|
||||||
|
|
||||||
|
$limit = $this->getInput('limit') ?: -1;
|
||||||
|
|
||||||
|
foreach($articles as $article) {
|
||||||
|
|
||||||
|
$anchor = $article->find('a', 0)
|
||||||
|
or returnServerError('Could not find anchor!');
|
||||||
|
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
$item['uri'] = $this->getTarget($anchor);
|
||||||
|
$item['title'] = trim($anchor->plaintext);
|
||||||
|
|
||||||
|
// The timestamp is encoded as relative time (max. the last 48 hours)
|
||||||
|
// like this: "- 7 hours". It should always be at the end of the article:
|
||||||
|
$age = substr($article->plaintext, strrpos($article->plaintext, '-'));
|
||||||
|
|
||||||
|
$item['timestamp'] = strtotime($age);
|
||||||
|
$item['author'] = $author;
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
|
||||||
|
if($limit > 0 && count($this->items) >= $limit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractHotTopics($html) {
|
||||||
|
|
||||||
|
$topics = $html->find('#popbox ul li')
|
||||||
|
or returnServerError('Could not find topics!');
|
||||||
|
|
||||||
|
$limit = $this->getInput('limit') ?: -1;
|
||||||
|
|
||||||
|
foreach($topics as $topic) {
|
||||||
|
|
||||||
|
$anchor = $topic->find('a', 0)
|
||||||
|
or returnServerError('Could not find anchor!');
|
||||||
|
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
$item['uri'] = $this->getTarget($anchor);
|
||||||
|
$item['title'] = $anchor->title;
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
|
||||||
|
if($limit > 0 && count($this->items) >= $limit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractCustomFeed($html) {
|
||||||
|
|
||||||
|
$boxes = $html->find('#boxx .boxes')
|
||||||
|
or returnServerError('Could not find boxes!');
|
||||||
|
|
||||||
|
foreach($boxes as $box) {
|
||||||
|
|
||||||
|
$anchor = $box->find('span.boxtitles a', 0)
|
||||||
|
or returnServerError('Could not find box anchor!');
|
||||||
|
|
||||||
|
$author = '<a href="' . $anchor->href . '">' . trim($anchor->plaintext) . '</a>';
|
||||||
|
$uri = $anchor->href;
|
||||||
|
|
||||||
|
$box_html = getSimpleHTMLDOM($uri)
|
||||||
|
or returnServerError('Could not load custom feed!');
|
||||||
|
|
||||||
|
$this->extractFeed($box_html, $author);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTarget($anchor) {
|
||||||
|
|
||||||
|
// Anchors are linked to Skimfeed, luckily the target URI is encoded
|
||||||
|
// in that URI via '&u=<URI>':
|
||||||
|
$query = parse_url($anchor->href, PHP_URL_QUERY);
|
||||||
|
|
||||||
|
foreach(explode('&', $query) as $parameter) {
|
||||||
|
|
||||||
|
list($key, $value) = explode('=', $parameter);
|
||||||
|
|
||||||
|
if($key !== 'u') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return urldecode($value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dev-mode!
|
||||||
|
* Requires '&format=Html'
|
||||||
|
*
|
||||||
|
* Returns the 'box' array from the source site
|
||||||
|
*/
|
||||||
|
private function exportBoxChannels() {
|
||||||
|
$html = getSimpleHTMLDOMCached(static::URI)
|
||||||
|
or returnServerError('No contents received from Skimfeed!');
|
||||||
|
|
||||||
|
if(!$this->isCompatible($html)) {
|
||||||
|
returnServerError('Skimfeed version is not compatible!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$boxes = $html->find('#boxx .boxes')
|
||||||
|
or returnServerError('Could not find boxes!');
|
||||||
|
|
||||||
|
// begin of 'channel' list
|
||||||
|
$message = <<<EOD
|
||||||
|
'box_channel' => array(
|
||||||
|
'name' => 'Channel',
|
||||||
|
'type' => 'list',
|
||||||
|
'required' => true,
|
||||||
|
'title' => 'Select your channel',
|
||||||
|
'values' => array(
|
||||||
|
|
||||||
|
EOD;
|
||||||
|
|
||||||
|
foreach($boxes as $box) {
|
||||||
|
|
||||||
|
$anchor = $box->find('span.boxtitles a', 0)
|
||||||
|
or returnServerError('Could not find box anchor!');
|
||||||
|
|
||||||
|
$title = trim($anchor->plaintext);
|
||||||
|
$uri = $anchor->href;
|
||||||
|
|
||||||
|
// add value
|
||||||
|
$message .= "\t\t'{$title}' => '{$uri}', \n";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// end of 'box' list
|
||||||
|
$message .= <<<EOD
|
||||||
|
)
|
||||||
|
),
|
||||||
|
EOD;
|
||||||
|
|
||||||
|
echo <<<EOD
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<code style="white-space: pre-wrap;">{$message}</code>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOD;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dev-mode!
|
||||||
|
* Requires '&format=Html'
|
||||||
|
*
|
||||||
|
* Returns the 'techs' array from the source site
|
||||||
|
*/
|
||||||
|
private function exportTechChannels() {
|
||||||
|
$html = getSimpleHTMLDOMCached(static::URI)
|
||||||
|
or returnServerError('No contents received from Skimfeed!');
|
||||||
|
|
||||||
|
if(!$this->isCompatible($html)) {
|
||||||
|
returnServerError('Skimfeed version is not compatible!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$channels = $html->find('#menubar a')
|
||||||
|
or returnServerError('Could not find channels!');
|
||||||
|
|
||||||
|
// begin of 'tech_channel' list
|
||||||
|
$message = <<<EOD
|
||||||
|
'tech_channel' => array(
|
||||||
|
'name' => 'Tech channel',
|
||||||
|
'type' => 'list',
|
||||||
|
'required' => true,
|
||||||
|
'title' => 'Select your tech channel',
|
||||||
|
'values' => array(
|
||||||
|
|
||||||
|
EOD;
|
||||||
|
|
||||||
|
foreach($channels as $channel) {
|
||||||
|
|
||||||
|
if($channel->href === '#'
|
||||||
|
|| $channel->class === 'homelink'
|
||||||
|
|| $channel->plaintext === 'Twitter'
|
||||||
|
|| $channel->plaintext === 'Weather'
|
||||||
|
|| $channel->plaintext === '+Custom') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = trim($channel->plaintext);
|
||||||
|
$uri = '/' . $channel->href;
|
||||||
|
|
||||||
|
$message .= "\t\t'{$title}' => array(\n";
|
||||||
|
|
||||||
|
$channel_html = getSimpleHTMLDOMCached(static::URI . $uri)
|
||||||
|
or returnServerError('Could not load tech channel ' . $channel->plaintext . '!');
|
||||||
|
|
||||||
|
$boxes = $channel_html->find('#boxx .boxes')
|
||||||
|
or returnServerError('Could not find boxes!');
|
||||||
|
|
||||||
|
foreach($boxes as $box) {
|
||||||
|
|
||||||
|
$anchor = $box->find('span.boxtitles a', 0)
|
||||||
|
or returnServerError('Could not find box anchor!');
|
||||||
|
|
||||||
|
$boxtitle = trim($anchor->plaintext);
|
||||||
|
$boxuri = $anchor->href;
|
||||||
|
|
||||||
|
$message .= "\t\t\t'{$boxtitle}' => '{$boxuri}', \n";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$message .= "\t\t),\n";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// end of 'box' list
|
||||||
|
$message .= <<<EOD
|
||||||
|
)
|
||||||
|
),
|
||||||
|
EOD;
|
||||||
|
|
||||||
|
echo <<<EOD
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<code style="white-space: pre-wrap;">{$message}</code>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the reported skimfeed version is compatible
|
||||||
|
*/
|
||||||
|
private function isCompatible($html) {
|
||||||
|
$title = $html->find('title', 0);
|
||||||
|
|
||||||
|
if(!$title) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($title->plaintext === 'Skimfeed V5.5 - Tech News') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -14,7 +14,7 @@ class SoundCloudBridge extends BridgeAbstract {
|
|||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
|
||||||
const CLIENT_ID = '0aca19eae3843844e4053c6d8fdb7875';
|
const CLIENT_ID = '4jkoEFmZEDaqjwJ9Eih4ATNhcH3vMVfp';
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
45
bridges/SuperSmashBlogBridge.php
Normal file
45
bridges/SuperSmashBlogBridge.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
class SuperSmashBlogBridge extends BridgeAbstract {
|
||||||
|
|
||||||
|
const MAINTAINER = 'corenting';
|
||||||
|
const NAME = 'Super Smash Blog';
|
||||||
|
const URI = 'https://www.smashbros.com/en_US/blog/index.html';
|
||||||
|
const CACHE_TIMEOUT = 7200; // 2h
|
||||||
|
const DESCRIPTION = 'Latest articles from the Super Smash Blog blog';
|
||||||
|
|
||||||
|
public function collectData(){
|
||||||
|
$dlUrl = 'https://www.smashbros.com/data/bs/en_US/json/en_US.json';
|
||||||
|
|
||||||
|
$jsonString = getContents($dlUrl) or returnServerError('Error while downloading the website content');
|
||||||
|
$json = json_decode($jsonString, true);
|
||||||
|
|
||||||
|
foreach($json as $article) {
|
||||||
|
|
||||||
|
// Build content
|
||||||
|
$picture = $article['acf']['image1']['url'];
|
||||||
|
if (strlen($picture) != 0) {
|
||||||
|
$picture = str_get_html('<img src="https://www.smashbros.com/' . substr($picture, 8) . '"/>');
|
||||||
|
} else {
|
||||||
|
$picture = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$video = $article['acf']['link_url'];
|
||||||
|
if (strlen($video) != 0) {
|
||||||
|
$video = str_get_html('<a href="' . $video .'">Youtube video</a>');
|
||||||
|
} else {
|
||||||
|
$video = '';
|
||||||
|
}
|
||||||
|
$text = str_get_html($article['acf']['editor']);
|
||||||
|
$content = $picture . $video . $text;
|
||||||
|
|
||||||
|
// Build final item
|
||||||
|
$item = array();
|
||||||
|
$item['title'] = $article['title']['rendered'];
|
||||||
|
$item['timestamp'] = strtotime($article['date']);
|
||||||
|
$item['content'] = $content;
|
||||||
|
$item['uri'] = self::URI . '?post=' . $article['id'];
|
||||||
|
|
||||||
|
$this->items[] = $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++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,102 +0,0 @@
|
|||||||
<?php
|
|
||||||
class Torrent9Bridge extends BridgeAbstract {
|
|
||||||
|
|
||||||
const MAINTAINER = 'lagaisse';
|
|
||||||
const NAME = 'Torrent9 Bridge';
|
|
||||||
const URI = 'http://www.torrent9.pe';
|
|
||||||
const CACHE_TIMEOUT = 86400; // 24h = 86400s
|
|
||||||
const DESCRIPTION = 'Returns latest torrents';
|
|
||||||
|
|
||||||
const PAGE_SERIES = 'torrents_series';
|
|
||||||
const PAGE_SERIES_VOSTFR = 'torrents_series_vostfr';
|
|
||||||
const PAGE_SERIES_FR = 'torrents_series_french';
|
|
||||||
|
|
||||||
const PARAMETERS = array(
|
|
||||||
'From search' => array(
|
|
||||||
'q' => array(
|
|
||||||
'name' => 'Search',
|
|
||||||
'required' => true,
|
|
||||||
'title' => 'Type your search'
|
|
||||||
)
|
|
||||||
),
|
|
||||||
'By page' => array(
|
|
||||||
'page' => array(
|
|
||||||
'name' => 'Page',
|
|
||||||
'type' => 'list',
|
|
||||||
'required' => false,
|
|
||||||
'values' => array(
|
|
||||||
'Series' => self::PAGE_SERIES,
|
|
||||||
'Series VOST' => self::PAGE_SERIES_VOSTFR,
|
|
||||||
'Series FR' => self::PAGE_SERIES_FR,
|
|
||||||
),
|
|
||||||
'defaultValue' => self::PAGE_SERIES
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
public function collectData(){
|
|
||||||
|
|
||||||
if($this->queriedContext === 'From search') {
|
|
||||||
$request = str_replace(' ', '-', trim($this->getInput('q')));
|
|
||||||
$page = self::URI . '/search_torrent/' . urlencode($request) . '.html';
|
|
||||||
} else {
|
|
||||||
$request = $this->getInput('page');
|
|
||||||
$page = self::URI . '/' . $request . '.html';
|
|
||||||
}
|
|
||||||
|
|
||||||
$html = getSimpleHTMLDOM($page)
|
|
||||||
or returnServerError('No results for this query.');
|
|
||||||
|
|
||||||
foreach($html->find('table', 0)->find('tr') as $episode) {
|
|
||||||
if($episode->parent->tag == 'tbody') {
|
|
||||||
|
|
||||||
$urlepisode = self::URI . $episode->find('a', 0)->getAttribute('href');
|
|
||||||
|
|
||||||
//30 years = forever
|
|
||||||
$htmlepisode = getSimpleHTMLDOMCached($urlepisode, 86400 * 366 * 30);
|
|
||||||
|
|
||||||
$item = array();
|
|
||||||
$item['author'] = $episode->find('a', 0)->text();
|
|
||||||
$item['title'] = $episode->find('a', 0)->text();
|
|
||||||
$item['id'] = $episode->find('a', 0)->getAttribute('href');
|
|
||||||
$item['pubdate'] = $this->getCachedDate($urlepisode);
|
|
||||||
|
|
||||||
$textefiche = $htmlepisode->find('.movie-information', 0)->find('p', 1);
|
|
||||||
if(isset($textefiche)) {
|
|
||||||
$item['content'] = $textefiche->text();
|
|
||||||
} else {
|
|
||||||
$p = $htmlepisode->find('.movie-information', 0)->find('p');
|
|
||||||
if(!empty($p)) {
|
|
||||||
$item['content'] = $htmlepisode->find('.movie-information', 0)->find('p', 0)->text();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$item['id'] = $episode->find('a', 0)->getAttribute('href');
|
|
||||||
$item['uri'] = self::URI . $htmlepisode->find('.download', 0)->getAttribute('href');
|
|
||||||
|
|
||||||
$this->items[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function getName(){
|
|
||||||
if(!is_null($this->getInput('q'))) {
|
|
||||||
return $this->getInput('q') . ' : ' . self::NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent::getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getCachedDate($url){
|
|
||||||
debugMessage('getting pubdate from url ' . $url . '');
|
|
||||||
// Initialize cache
|
|
||||||
$cache = Cache::create('FileCache');
|
|
||||||
$cache->setPath(CACHE_DIR . '/pages');
|
|
||||||
$params = [$url];
|
|
||||||
$cache->setParameters($params);
|
|
||||||
// Get cachefile timestamp
|
|
||||||
$time = $cache->getTime();
|
|
||||||
return ($time !== false ? $time : time());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -43,40 +43,274 @@ class VkBridge extends BridgeAbstract
|
|||||||
or returnServerError('No results for group or user name "' . $this->getInput('u') . '".');
|
or returnServerError('No results for group or user name "' . $this->getInput('u') . '".');
|
||||||
|
|
||||||
$text_html = iconv('windows-1251', 'utf-8', $text_html);
|
$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);
|
$html = str_get_html($text_html);
|
||||||
$pageName = $html->find('.page_name', 0)->plaintext;
|
$pageName = $html->find('.page_name', 0);
|
||||||
$this->pageName = $pageName;
|
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) {
|
||||||
|
|
||||||
|
$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))) {
|
if (is_object($post->find('a.wall_post_more', 0))) {
|
||||||
//delete link "show full" in content
|
//delete link "show full" in content
|
||||||
$post->find('a.wall_post_more', 0)->outertext = '';
|
$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))) {
|
$content_suffix = '';
|
||||||
$link = $post->find('a.page_media_link_title', 0)->getAttribute('href');
|
|
||||||
//external link in the post
|
// looking for external links
|
||||||
$item['content'] .= "\n\rExternal link: "
|
$external_link_selectors = array(
|
||||||
. str_replace('/away.php?to=', '', urldecode($link));
|
'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>";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//get video on post
|
// remove external link from content
|
||||||
if (is_object($post->find('span.post_video_title_content', 0))) {
|
$external_link_selectors_to_remove = array(
|
||||||
$titleVideo = $post->find('span.post_video_title_content', 0)->plaintext;
|
'div.page_media_thumbed_link',
|
||||||
$linkToVideo = self::URI . $post->find('a.page_post_thumb_video', 0)->getAttribute('href');
|
'div.page_media_link_desc_wrap',
|
||||||
$item['content'] .= "\n\r {$titleVideo}: {$linkToVideo}";
|
'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>');
|
||||||
|
$item['content'] .= $content_suffix;
|
||||||
|
$item['categories'] = array();
|
||||||
|
|
||||||
|
// get post hashtags
|
||||||
|
foreach($post->find('a') as $a) {
|
||||||
|
$href = $a->getAttribute('href');
|
||||||
|
$prefix = '/feed?section=search&q=%23';
|
||||||
|
$innertext = $a->innertext;
|
||||||
|
if ($href && substr($href, 0, strlen($prefix)) === $prefix) {
|
||||||
|
$item['categories'][] = urldecode(substr($href, strlen($prefix)));
|
||||||
|
} else if (substr($innertext, 0, 1) == '#') {
|
||||||
|
$item['categories'][] = $innertext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get post link
|
// get post link
|
||||||
$item['uri'] = self::URI . $post->find('a.post_link', 0)->getAttribute('href');
|
$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['timestamp'] = $this->getTime($post);
|
||||||
$item['author'] = $pageName;
|
$item['title'] = $this->getTitle($item['content']);
|
||||||
$this->items[] = $item;
|
$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)
|
private function getTime($post)
|
||||||
|
@@ -18,10 +18,10 @@ class WhydBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
$html = '';
|
$html = '';
|
||||||
if(strlen(preg_replace("/[^0-9a-f]/", '', $this->getInput('u'))) == 24) {
|
if(strlen(preg_replace('/[^0-9a-f]/', '', $this->getInput('u'))) == 24) {
|
||||||
// is input the userid ?
|
// is input the userid ?
|
||||||
$html = getSimpleHTMLDOM(
|
$html = getSimpleHTMLDOM(
|
||||||
self::URI . 'u/' . preg_replace("/[^0-9a-f]/", '', $this->getInput('u'))
|
self::URI . 'u/' . preg_replace('/[^0-9a-f]/', '', $this->getInput('u'))
|
||||||
) or returnServerError('No results for this query.');
|
) or returnServerError('No results for this query.');
|
||||||
} else { // input may be the username
|
} else { // input may be the username
|
||||||
$html = getSimpleHTMLDOM(
|
$html = getSimpleHTMLDOM(
|
||||||
|
@@ -1,16 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
class WorldOfTanksBridge extends BridgeAbstract {
|
class WorldOfTanksBridge extends FeedExpander {
|
||||||
|
|
||||||
const MAINTAINER = 'mitsukarenai';
|
const MAINTAINER = 'Riduidel';
|
||||||
const NAME = 'World of Tanks';
|
const NAME = 'World of Tanks';
|
||||||
const URI = 'http://worldoftanks.eu/';
|
const URI = 'http://worldoftanks.eu/';
|
||||||
const DESCRIPTION = 'News about the tank slaughter game.';
|
const DESCRIPTION = 'News about the tank slaughter game.';
|
||||||
|
|
||||||
const PARAMETERS = array( array(
|
const PARAMETERS = array( array(
|
||||||
'category' => array(
|
|
||||||
// TODO: should be a list
|
|
||||||
'name' => 'nom de la catégorie'
|
|
||||||
),
|
|
||||||
'lang' => array(
|
'lang' => array(
|
||||||
'name' => 'Langue',
|
'name' => 'Langue',
|
||||||
'type' => 'list',
|
'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(){
|
protected function parseItem($newsItem){
|
||||||
if(!is_null($this->getInput('lang'))) {
|
$item = parent::parseItem($newsItem);
|
||||||
$lang = $this->getInput('lang');
|
$item['content'] = $this->loadFullArticle($item['uri']);
|
||||||
$uri = self::URI . $lang . '/news/';
|
return $item;
|
||||||
if(!empty($this->getInput('category'))) {
|
}
|
||||||
$uri .= 'pc-browser/' . $this->getInput('category') . '/';
|
|
||||||
}
|
/**
|
||||||
return $uri;
|
* 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();
|
return $content->innertext;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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&order=desc&sort=publish_date')
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@@ -25,14 +25,14 @@ class YoutubeBridge extends BridgeAbstract {
|
|||||||
'By channel id' => array(
|
'By channel id' => array(
|
||||||
'c' => array(
|
'c' => array(
|
||||||
'name' => 'channel id',
|
'name' => 'channel id',
|
||||||
'exampleValue' => "15",
|
'exampleValue' => '15',
|
||||||
'required' => true
|
'required' => true
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
'By playlist Id' => array(
|
'By playlist Id' => array(
|
||||||
'p' => array(
|
'p' => array(
|
||||||
'name' => 'playlist id',
|
'name' => 'playlist id',
|
||||||
'exampleValue' => "15"
|
'exampleValue' => '15'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
'Search result' => array(
|
'Search result' => array(
|
||||||
@@ -45,9 +45,25 @@ class YoutubeBridge extends BridgeAbstract {
|
|||||||
'type' => 'number',
|
'type' => 'number',
|
||||||
'exampleValue' => 1
|
'exampleValue' => 1
|
||||||
)
|
)
|
||||||
|
),
|
||||||
|
'global' => array(
|
||||||
|
'duration_min' => array(
|
||||||
|
'name' => 'min. duration (minutes)',
|
||||||
|
'type' => 'number',
|
||||||
|
'title' => 'Minimum duration for the video in minutes',
|
||||||
|
'exampleValue' => 5
|
||||||
|
),
|
||||||
|
'duration_max' => array(
|
||||||
|
'name' => 'max. duration (minutes)',
|
||||||
|
'type' => 'number',
|
||||||
|
'title' => 'Maximum duration for the video in minutes',
|
||||||
|
'exampleValue' => 10
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private $feedName = '';
|
||||||
|
|
||||||
private function ytBridgeQueryVideoInfo($vid, &$author, &$desc, &$time){
|
private function ytBridgeQueryVideoInfo($vid, &$author, &$desc, &$time){
|
||||||
$html = $this->ytGetSimpleHTMLDOM(self::URI . "watch?v=$vid");
|
$html = $this->ytGetSimpleHTMLDOM(self::URI . "watch?v=$vid");
|
||||||
|
|
||||||
@@ -113,6 +129,17 @@ class YoutubeBridge extends BridgeAbstract {
|
|||||||
private function ytBridgeParseHtmlListing($html, $element_selector, $title_selector, $add_parsed_items = true) {
|
private function ytBridgeParseHtmlListing($html, $element_selector, $title_selector, $add_parsed_items = true) {
|
||||||
$limit = $add_parsed_items ? 10 : INF;
|
$limit = $add_parsed_items ? 10 : INF;
|
||||||
$count = 0;
|
$count = 0;
|
||||||
|
|
||||||
|
$duration_min = $this->getInput('duration_min') ?: -1;
|
||||||
|
$duration_min = $duration_min * 60;
|
||||||
|
|
||||||
|
$duration_max = $this->getInput('duration_max') ?: INF;
|
||||||
|
$duration_max = $duration_max * 60;
|
||||||
|
|
||||||
|
if($duration_max < $duration_min) {
|
||||||
|
returnClientError('Max duration must be greater than min duration!');
|
||||||
|
}
|
||||||
|
|
||||||
foreach($html->find($element_selector) as $element) {
|
foreach($html->find($element_selector) as $element) {
|
||||||
if($count < $limit) {
|
if($count < $limit) {
|
||||||
$author = '';
|
$author = '';
|
||||||
@@ -121,6 +148,20 @@ class YoutubeBridge extends BridgeAbstract {
|
|||||||
$vid = str_replace('/watch?v=', '', $element->find('a', 0)->href);
|
$vid = str_replace('/watch?v=', '', $element->find('a', 0)->href);
|
||||||
$vid = substr($vid, 0, strpos($vid, '&') ?: strlen($vid));
|
$vid = substr($vid, 0, strpos($vid, '&') ?: strlen($vid));
|
||||||
$title = $this->ytBridgeFixTitle($element->find($title_selector, 0)->plaintext);
|
$title = $this->ytBridgeFixTitle($element->find($title_selector, 0)->plaintext);
|
||||||
|
|
||||||
|
// The duration comes in one of the formats:
|
||||||
|
// hh:mm:ss / mm:ss / m:ss
|
||||||
|
// 01:03:30 / 15:06 / 1:24
|
||||||
|
$durationText = trim($element->find('span[class="video-time"]', 0)->plaintext);
|
||||||
|
$durationText = preg_replace('/([\d]{1,2})\:([\d]{2})/', '00:$1:$2', $durationText);
|
||||||
|
|
||||||
|
sscanf($durationText, '%d:%d:%d', $hours, $minutes, $seconds);
|
||||||
|
$duration = $hours * 3600 + $minutes * 60 + $seconds;
|
||||||
|
|
||||||
|
if($duration < $duration_min || $duration > $duration_max) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if($title != '[Private Video]' && strpos($vid, 'googleads') === false) {
|
if($title != '[Private Video]' && strpos($vid, 'googleads') === false) {
|
||||||
if ($add_parsed_items) {
|
if ($add_parsed_items) {
|
||||||
$this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time);
|
$this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time);
|
||||||
@@ -168,7 +209,7 @@ class YoutubeBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!empty($url_feed) && !empty($url_listing)) {
|
if(!empty($url_feed) && !empty($url_listing)) {
|
||||||
if($xml = $this->ytGetSimpleHTMLDOM($url_feed)) {
|
if(!$this->skipFeeds() && $xml = $this->ytGetSimpleHTMLDOM($url_feed)) {
|
||||||
$this->ytBridgeParseXmlFeed($xml);
|
$this->ytBridgeParseXmlFeed($xml);
|
||||||
} elseif($html = $this->ytGetSimpleHTMLDOM($url_listing)) {
|
} elseif($html = $this->ytGetSimpleHTMLDOM($url_listing)) {
|
||||||
$this->ytBridgeParseHtmlListing($html, 'li.channels-content-item', 'h3');
|
$this->ytBridgeParseHtmlListing($html, 'li.channels-content-item', 'h3');
|
||||||
@@ -182,7 +223,7 @@ class YoutubeBridge extends BridgeAbstract {
|
|||||||
$html = $this->ytGetSimpleHTMLDOM($url_listing)
|
$html = $this->ytGetSimpleHTMLDOM($url_listing)
|
||||||
or returnServerError("Could not request YouTube. Tried:\n - $url_listing");
|
or returnServerError("Could not request YouTube. Tried:\n - $url_listing");
|
||||||
$item_count = $this->ytBridgeParseHtmlListing($html, 'tr.pl-video', '.pl-video-title a', false);
|
$item_count = $this->ytBridgeParseHtmlListing($html, 'tr.pl-video', '.pl-video-title a', false);
|
||||||
if ($item_count <= 15 && ($xml = $this->ytGetSimpleHTMLDOM($url_feed))) {
|
if ($item_count <= 15 && !$this->skipFeeds() && ($xml = $this->ytGetSimpleHTMLDOM($url_feed))) {
|
||||||
$this->ytBridgeParseXmlFeed($xml);
|
$this->ytBridgeParseXmlFeed($xml);
|
||||||
} else {
|
} else {
|
||||||
$this->ytBridgeParseHtmlListing($html, 'tr.pl-video', '.pl-video-title a');
|
$this->ytBridgeParseHtmlListing($html, 'tr.pl-video', '.pl-video-title a');
|
||||||
@@ -195,7 +236,7 @@ class YoutubeBridge extends BridgeAbstract {
|
|||||||
$this->request = $this->getInput('s');
|
$this->request = $this->getInput('s');
|
||||||
$page = 1;
|
$page = 1;
|
||||||
if($this->getInput('pa'))
|
if($this->getInput('pa'))
|
||||||
$page = (int)preg_replace("/[^0-9]/", '', $this->getInput('pa'));
|
$page = (int)preg_replace('/[^0-9]/', '', $this->getInput('pa'));
|
||||||
|
|
||||||
$url_listing = self::URI
|
$url_listing = self::URI
|
||||||
. 'results?search_query='
|
. 'results?search_query='
|
||||||
@@ -207,7 +248,7 @@ class YoutubeBridge extends BridgeAbstract {
|
|||||||
$html = $this->ytGetSimpleHTMLDOM($url_listing)
|
$html = $this->ytGetSimpleHTMLDOM($url_listing)
|
||||||
or returnServerError("Could not request YouTube. Tried:\n - $url_listing");
|
or returnServerError("Could not request YouTube. Tried:\n - $url_listing");
|
||||||
|
|
||||||
$this->ytBridgeParseHtmlListing($html, 'div.yt-lockup', 'h3');
|
$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()
|
$this->feedName = 'Search: ' . str_replace(' - YouTube', '', $html->find('title', 0)->plaintext); // feedName will be used by getName()
|
||||||
} else { /* no valid mode */
|
} else { /* no valid mode */
|
||||||
returnClientError("You must either specify either:\n - YouTube
|
returnClientError("You must either specify either:\n - YouTube
|
||||||
@@ -215,6 +256,10 @@ class YoutubeBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function skipFeeds() {
|
||||||
|
return ($this->getInput('duration_min') || $this->getInput('duration_max'));
|
||||||
|
}
|
||||||
|
|
||||||
public function getName(){
|
public function getName(){
|
||||||
// Name depends on queriedContext:
|
// Name depends on queriedContext:
|
||||||
switch($this->queriedContext) {
|
switch($this->queriedContext) {
|
||||||
@@ -226,5 +271,5 @@ class YoutubeBridge extends BridgeAbstract {
|
|||||||
default:
|
default:
|
||||||
return parent::getName();
|
return parent::getName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
55
bridges/ZenodoBridge.php
Normal file
55
bridges/ZenodoBridge.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
class ZenodoBridge extends BridgeAbstract {
|
||||||
|
const MAINTAINER = 'theradialactive';
|
||||||
|
const NAME = 'Zenodo';
|
||||||
|
const URI = 'https://zenodo.org';
|
||||||
|
const CACHE_TIMEOUT = 10;
|
||||||
|
const DESCRIPTION = 'Returns the newest content of Zenodo';
|
||||||
|
|
||||||
|
public function collectData(){
|
||||||
|
$html = getSimpleHTMLDOM($this->getURI())
|
||||||
|
or returnServerError('zenodo.org not reachable.');
|
||||||
|
|
||||||
|
foreach($html->find('div.record-elem') as $element) {
|
||||||
|
$item = array();
|
||||||
|
$item['uri'] = self::URI . $element->find('h4', 0)->find('a', 0)->href;
|
||||||
|
$item['title'] = trim(
|
||||||
|
htmlspecialchars_decode($element->find('h4', 0)->find('a', 0)->innertext,
|
||||||
|
ENT_QUOTES
|
||||||
|
)
|
||||||
|
);
|
||||||
|
foreach($element->find('p', 0)->find('span') as $authors) {
|
||||||
|
$item['author'] = $item['author'] . $authors . '; ';
|
||||||
|
}
|
||||||
|
$content = $element->find('p.hidden-xs', 0)->find('a', 0)->innertext . '<br>';
|
||||||
|
$type = '<br>Type: ' . $element->find('span.label-default', 0)->innertext;
|
||||||
|
|
||||||
|
$raw_date = $element->find('small.text-muted', 0)->innertext;
|
||||||
|
$clean_date = date_parse(str_replace('Uploaded on ', '', $raw_date));
|
||||||
|
|
||||||
|
$content = $content . date_parse($clean_date);
|
||||||
|
|
||||||
|
$item['timestamp'] = mktime(
|
||||||
|
$clean_date['hour'],
|
||||||
|
$clean_date['minute'],
|
||||||
|
$clean_date['second'],
|
||||||
|
$clean_date['month'],
|
||||||
|
$clean_date['day'],
|
||||||
|
$clean_date['year']
|
||||||
|
);
|
||||||
|
|
||||||
|
$access = '';
|
||||||
|
if ($element->find('span.label-success', 0)->innertext) {
|
||||||
|
$access = 'Open Access';
|
||||||
|
} elseif ($element->find('span.label-warning', 0)->innertext) {
|
||||||
|
$access = 'Embargoed Access';
|
||||||
|
} else {
|
||||||
|
$access = $element->find('span.label-error', 0)->innertext;
|
||||||
|
}
|
||||||
|
$access = '<br>Access: ' . $access;
|
||||||
|
$publication = '<br>Publication Date: ' . $element->find('span.label-info', 0)->innertext;
|
||||||
|
$item['content'] = $content . $type . $access . $publication;
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -8,7 +8,9 @@ class FileCache implements CacheInterface {
|
|||||||
protected $param;
|
protected $param;
|
||||||
|
|
||||||
public function loadData(){
|
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){
|
public function saveData($datas){
|
||||||
@@ -17,7 +19,7 @@ class FileCache implements CacheInterface {
|
|||||||
$writeStream = file_put_contents($this->getCacheFile(), serialize($datas));
|
$writeStream = file_put_contents($this->getCacheFile(), serialize($datas));
|
||||||
|
|
||||||
if($writeStream === false) {
|
if($writeStream === false) {
|
||||||
throw new \Exception("Cannot write the cache... Do you have the right permissions ?");
|
throw new \Exception('Cannot write the cache... Do you have the right permissions ?');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
44
config.default.ini.php
Normal file
44
config.default.ini.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
; <?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
|
||||||
|
|
||||||
|
[authentication]
|
||||||
|
|
||||||
|
; Enables authentication for all requests to this RSS-Bridge instance.
|
||||||
|
;
|
||||||
|
; Warning: You'll have to upgrade existing feeds after enabling this option!
|
||||||
|
;
|
||||||
|
; true = enabled
|
||||||
|
; false = disabled (default)
|
||||||
|
enable = false
|
||||||
|
|
||||||
|
; The username for authentication. Insert this name when prompted for login.
|
||||||
|
username = ""
|
||||||
|
|
||||||
|
; The password for authentication. Insert this password when prompted for login.
|
||||||
|
; Use a strong password to prevent others from guessing your login!
|
||||||
|
password = ""
|
@@ -15,8 +15,11 @@ class AtomFormat extends FormatAbstract{
|
|||||||
|
|
||||||
$extraInfos = $this->getExtraInfos();
|
$extraInfos = $this->getExtraInfos();
|
||||||
$title = $this->xml_encode($extraInfos['name']);
|
$title = $this->xml_encode($extraInfos['name']);
|
||||||
$uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : 'https://github.com/sebsauvage/rss-bridge';
|
$uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : 'https://github.com/RSS-Bridge/rss-bridge';
|
||||||
$icon = $this->xml_encode('http://icons.better-idea.org/icon?url='. $uri .'&size=64');
|
|
||||||
|
$uriparts = parse_url($uri);
|
||||||
|
$icon = $this->xml_encode($uriparts['scheme'] . '://' . $uriparts['host'] .'/favicon.ico');
|
||||||
|
|
||||||
$uri = $this->xml_encode($uri);
|
$uri = $this->xml_encode($uri);
|
||||||
|
|
||||||
$entries = '';
|
$entries = '';
|
||||||
@@ -37,6 +40,16 @@ class AtomFormat extends FormatAbstract{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$entryCategories = '';
|
||||||
|
if(isset($item['categories'])) {
|
||||||
|
foreach($item['categories'] as $category) {
|
||||||
|
$entryCategories .= '<category term="'
|
||||||
|
. $this->xml_encode($category)
|
||||||
|
. '"/>'
|
||||||
|
. PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$entries .= <<<EOD
|
$entries .= <<<EOD
|
||||||
|
|
||||||
<entry>
|
<entry>
|
||||||
@@ -49,6 +62,7 @@ class AtomFormat extends FormatAbstract{
|
|||||||
<updated>{$entryTimestamp}</updated>
|
<updated>{$entryTimestamp}</updated>
|
||||||
<content type="html">{$entryContent}</content>
|
<content type="html">{$entryContent}</content>
|
||||||
{$entryEnclosures}
|
{$entryEnclosures}
|
||||||
|
{$entryCategories}
|
||||||
</entry>
|
</entry>
|
||||||
|
|
||||||
EOD;
|
EOD;
|
||||||
|
@@ -47,6 +47,20 @@ class HtmlFormat extends FormatAbstract {
|
|||||||
$entryEnclosures .= '</div>';
|
$entryEnclosures .= '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$entryCategories = '';
|
||||||
|
if(isset($item['categories'])) {
|
||||||
|
$entryCategories = '<div class="categories"><p>Categories:</p>';
|
||||||
|
|
||||||
|
foreach($item['categories'] as $category) {
|
||||||
|
|
||||||
|
$entryCategories .= '<li class="category">'
|
||||||
|
. $this->sanitizeHtml($category)
|
||||||
|
. '</li>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$entryCategories .= '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
$entries .= <<<EOD
|
$entries .= <<<EOD
|
||||||
|
|
||||||
<section class="feeditem">
|
<section class="feeditem">
|
||||||
@@ -55,6 +69,7 @@ class HtmlFormat extends FormatAbstract {
|
|||||||
{$entryAuthor}
|
{$entryAuthor}
|
||||||
{$entryContent}
|
{$entryContent}
|
||||||
{$entryEnclosures}
|
{$entryEnclosures}
|
||||||
|
{$entryCategories}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
EOD;
|
EOD;
|
||||||
|
@@ -18,10 +18,11 @@ class MrssFormat extends FormatAbstract {
|
|||||||
if(!empty($extraInfos['uri'])) {
|
if(!empty($extraInfos['uri'])) {
|
||||||
$uri = $this->xml_encode($extraInfos['uri']);
|
$uri = $this->xml_encode($extraInfos['uri']);
|
||||||
} else {
|
} 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');
|
$uriparts = parse_url($uri);
|
||||||
|
$icon = $this->xml_encode($uriparts['scheme'] . '://' . $uriparts['host'] .'/favicon.ico');
|
||||||
|
|
||||||
$items = '';
|
$items = '';
|
||||||
foreach($this->getItems() as $item) {
|
foreach($this->getItems() as $item) {
|
||||||
@@ -50,6 +51,16 @@ Some media files might not be shown to you. Consider using the ATOM format inste
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$entryCategories = '';
|
||||||
|
if(isset($item['categories'])) {
|
||||||
|
|
||||||
|
foreach($item['categories'] as $category) {
|
||||||
|
$entryCategories .= '<category>'
|
||||||
|
. $category . '</category>'
|
||||||
|
. PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$items .= <<<EOD
|
$items .= <<<EOD
|
||||||
|
|
||||||
<item>
|
<item>
|
||||||
@@ -60,6 +71,7 @@ Some media files might not be shown to you. Consider using the ATOM format inste
|
|||||||
<description>{$itemContent}{$entryEnclosuresWarning}</description>
|
<description>{$itemContent}{$entryEnclosuresWarning}</description>
|
||||||
<author>{$itemAuthor}</author>
|
<author>{$itemAuthor}</author>
|
||||||
{$entryEnclosures}
|
{$entryEnclosures}
|
||||||
|
{$entryCategories}
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
EOD;
|
EOD;
|
||||||
|
148
index.php
148
index.php
@@ -1,37 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
/*
|
require_once __DIR__ . '/lib/RssBridge.php';
|
||||||
TODO :
|
|
||||||
- factorize the annotation system
|
|
||||||
- factorize to adapter : Format, Bridge, Cache(actually code is almost the same)
|
|
||||||
- implement annotation cache for entrance page
|
|
||||||
- Cache : I think logic must be change as least to avoid to reconvert object from json in FileCache case.
|
|
||||||
- add namespace to avoid futur problem ?
|
|
||||||
- see FIXME mentions in the code
|
|
||||||
- implement header('X-Cached-Version: '.date(DATE_ATOM, filemtime($cachefile)));
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Defines the minimum required PHP version for RSS-Bridge
|
|
||||||
define('PHP_VERSION_REQUIRED', '5.6.0');
|
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');
|
|
||||||
|
|
||||||
// Allows the operator to specify custom cache timeouts via '&_cache_timeout=3600'
|
|
||||||
// true: enabled, false: disabled (default)
|
|
||||||
define('CUSTOM_CACHE_TIMEOUT', false);
|
|
||||||
|
|
||||||
date_default_timezone_set('UTC');
|
|
||||||
error_reporting(0);
|
|
||||||
|
|
||||||
// Specify directory for cached files (using FileCache)
|
// Specify directory for cached files (using FileCache)
|
||||||
define('CACHE_DIR', __DIR__ . '/cache');
|
define('CACHE_DIR', __DIR__ . '/cache');
|
||||||
|
|
||||||
// Specify path for whitelist file
|
// Specify path for whitelist file
|
||||||
define('WHITELIST_FILE', __DIR__ . '/whitelist.txt');
|
define('WHITELIST_FILE', __DIR__ . '/whitelist.txt');
|
||||||
|
|
||||||
|
Configuration::verifyInstallation();
|
||||||
|
Configuration::loadConfiguration();
|
||||||
|
|
||||||
|
Authentication::showPromptIfNeeded();
|
||||||
|
|
||||||
|
date_default_timezone_set('UTC');
|
||||||
|
error_reporting(0);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Move the CLI arguments to the $_GET array, in order to be able to use
|
Move the CLI arguments to the $_GET array, in order to be able to use
|
||||||
@@ -61,40 +45,6 @@ if(file_exists('DEBUG')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once __DIR__ . '/lib/RssBridge.php';
|
|
||||||
|
|
||||||
// Check PHP version
|
|
||||||
if(version_compare(PHP_VERSION, PHP_VERSION_REQUIRED) === -1)
|
|
||||||
die('RSS-Bridge requires at least PHP version ' . PHP_VERSION_REQUIRED . '!');
|
|
||||||
|
|
||||||
// extensions check
|
|
||||||
if(!extension_loaded('openssl'))
|
|
||||||
die('"openssl" extension not loaded. Please check "php.ini"');
|
|
||||||
|
|
||||||
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
|
// 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)';
|
$userAgent = 'Mozilla/5.0(X11; Linux x86_64; rv:30.0)';
|
||||||
@@ -145,6 +95,7 @@ try {
|
|||||||
$whitelist_selection = array_map('strtolower', $whitelist_selection);
|
$whitelist_selection = array_map('strtolower', $whitelist_selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN);
|
||||||
$action = array_key_exists('action', $params) ? $params['action'] : null;
|
$action = array_key_exists('action', $params) ? $params['action'] : null;
|
||||||
$bridge = array_key_exists('bridge', $params) ? $params['bridge'] : null;
|
$bridge = array_key_exists('bridge', $params) ? $params['bridge'] : null;
|
||||||
|
|
||||||
@@ -230,8 +181,8 @@ try {
|
|||||||
header('Content-Type: text/html');
|
header('Content-Type: text/html');
|
||||||
die(buildBridgeException($e, $bridge));
|
die(buildBridgeException($e, $bridge));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
die;
|
echo BridgeList::create($whitelist_selection, $showInactive);
|
||||||
}
|
}
|
||||||
} catch(HttpException $e) {
|
} catch(HttpException $e) {
|
||||||
http_response_code($e->getCode());
|
http_response_code($e->getCode());
|
||||||
@@ -240,80 +191,3 @@ try {
|
|||||||
} catch(\Exception $e) {
|
} catch(\Exception $e) {
|
||||||
die($e->getMessage());
|
die($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
$formats = Format::searchInformation();
|
|
||||||
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<meta name="description" content="Rss-bridge" />
|
|
||||||
<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 {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</noscript>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body onload="search()">
|
|
||||||
<?php
|
|
||||||
$status = '';
|
|
||||||
if(defined('DEBUG') && DEBUG === true) {
|
|
||||||
$status .= 'debug mode active';
|
|
||||||
}
|
|
||||||
|
|
||||||
$query = filter_input(INPUT_GET, 'q');
|
|
||||||
|
|
||||||
echo <<<EOD
|
|
||||||
<header>
|
|
||||||
<h1>RSS-Bridge</h1>
|
|
||||||
<h2>·Reconnecting the Web·</h2>
|
|
||||||
<p class="status">{$status}</p>
|
|
||||||
</header>
|
|
||||||
<section class="searchbar">
|
|
||||||
<h3>Search</h3>
|
|
||||||
<input type="text" name="searchfield"
|
|
||||||
id="searchfield" placeholder="Enter the bridge you want to search for"
|
|
||||||
onchange="search()" onkeyup="search()" value="{$query}">
|
|
||||||
</section>
|
|
||||||
|
|
||||||
EOD;
|
|
||||||
|
|
||||||
$activeFoundBridgeCount = 0;
|
|
||||||
$showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN);
|
|
||||||
$inactiveBridges = '';
|
|
||||||
$bridgeList = Bridge::listBridges();
|
|
||||||
foreach($bridgeList as $bridgeName) {
|
|
||||||
if(Bridge::isWhitelisted($whitelist_selection, strtolower($bridgeName))) {
|
|
||||||
echo displayBridgeCard($bridgeName, $formats);
|
|
||||||
$activeFoundBridgeCount++;
|
|
||||||
} elseif($showInactive) {
|
|
||||||
// inactive bridges
|
|
||||||
$inactiveBridges .= displayBridgeCard($bridgeName, $formats, false) . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
echo $inactiveBridges;
|
|
||||||
?>
|
|
||||||
<section class="footer">
|
|
||||||
<a href="https://github.com/RSS-Bridge/rss-bridge">RSS-Bridge 2018-04-06 ~ Public Domain</a><br />
|
|
||||||
<?= $activeFoundBridgeCount; ?>/<?= count($bridgeList) ?> active bridges. <br />
|
|
||||||
<?php
|
|
||||||
if($activeFoundBridgeCount !== count($bridgeList)) {
|
|
||||||
// FIXME: This should be done in pure CSS
|
|
||||||
if(!$showInactive)
|
|
||||||
echo '<a href="?show_inactive=1"><button class="small">Show inactive bridges</button></a><br />';
|
|
||||||
else
|
|
||||||
echo '<a href="?show_inactive=0"><button class="small">Hide inactive bridges</button></a><br />';
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</section>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
31
lib/Authentication.php
Normal file
31
lib/Authentication.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
class Authentication {
|
||||||
|
|
||||||
|
public static function showPromptIfNeeded() {
|
||||||
|
|
||||||
|
if(Configuration::getConfig('authentication', 'enable') === true) {
|
||||||
|
if(!Authentication::verifyPrompt()) {
|
||||||
|
header('WWW-Authenticate: Basic realm="RSS-Bridge"');
|
||||||
|
header('HTTP/1.0 401 Unauthorized');
|
||||||
|
die('Please authenticate in order to access this instance !');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function verifyPrompt() {
|
||||||
|
|
||||||
|
if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
|
||||||
|
if(Configuration::getConfig('authentication', 'username') === $_SERVER['PHP_AUTH_USER']
|
||||||
|
&& Configuration::getConfig('authentication', 'password') === $_SERVER['PHP_AUTH_PW']) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
error_log('[RSS-Bridge] Failed authentication attempt from ' . $_SERVER['REMOTE_ADDR']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
259
lib/BridgeCard.php
Normal file
259
lib/BridgeCard.php
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
<?php
|
||||||
|
final class BridgeCard {
|
||||||
|
|
||||||
|
private static function buildFormatButtons($formats) {
|
||||||
|
$buttons = '';
|
||||||
|
|
||||||
|
foreach($formats as $name) {
|
||||||
|
$buttons .= '<button type="submit" name="format" value="'
|
||||||
|
. $name
|
||||||
|
. '">'
|
||||||
|
. $name
|
||||||
|
. '</button>'
|
||||||
|
. PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getFormHeader($bridgeName, $isHttps = false) {
|
||||||
|
$form = <<<EOD
|
||||||
|
<form method="GET" action="?">
|
||||||
|
<input type="hidden" name="action" value="display" />
|
||||||
|
<input type="hidden" name="bridge" value="{$bridgeName}" />
|
||||||
|
EOD;
|
||||||
|
|
||||||
|
if(!$isHttps) {
|
||||||
|
$form .= '<div class="secure-warning">Warning :
|
||||||
|
This bridge is not fetching its content through a secure connection</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getForm($bridgeName,
|
||||||
|
$formats,
|
||||||
|
$isActive = false,
|
||||||
|
$isHttps = false,
|
||||||
|
$parameterName = '',
|
||||||
|
$parameters = array()) {
|
||||||
|
$form = BridgeCard::getFormHeader($bridgeName, $isHttps);
|
||||||
|
|
||||||
|
foreach($parameters as $id => $inputEntry) {
|
||||||
|
if(!isset($inputEntry['exampleValue']))
|
||||||
|
$inputEntry['exampleValue'] = '';
|
||||||
|
|
||||||
|
if(!isset($inputEntry['defaultValue']))
|
||||||
|
$inputEntry['defaultValue'] = '';
|
||||||
|
|
||||||
|
$idArg = 'arg-'
|
||||||
|
. urlencode($bridgeName)
|
||||||
|
. '-'
|
||||||
|
. urlencode($parameterName)
|
||||||
|
. '-'
|
||||||
|
. urlencode($id);
|
||||||
|
|
||||||
|
$form .= '<label for="'
|
||||||
|
. $idArg
|
||||||
|
. '">'
|
||||||
|
. filter_var($inputEntry['name'], FILTER_SANITIZE_STRING)
|
||||||
|
. ' : </label>'
|
||||||
|
. PHP_EOL;
|
||||||
|
|
||||||
|
if(!isset($inputEntry['type']) || $inputEntry['type'] === 'text') {
|
||||||
|
$form .= BridgeCard::getTextInput($inputEntry, $idArg, $id);
|
||||||
|
} elseif($inputEntry['type'] === 'number') {
|
||||||
|
$form .= BridgeCard::getNumberInput($inputEntry, $idArg, $id);
|
||||||
|
} else if($inputEntry['type'] === 'list') {
|
||||||
|
$form .= BridgeCard::getListInput($inputEntry, $idArg, $id);
|
||||||
|
} elseif($inputEntry['type'] === 'checkbox') {
|
||||||
|
$form .= BridgeCard::getCheckboxInput($inputEntry, $idArg, $id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($isActive) {
|
||||||
|
$form .= BridgeCard::buildFormatButtons($formats);
|
||||||
|
} else {
|
||||||
|
$form .= '<span style="font-weight: bold;">Inactive</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $form . '</form>' . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getInputAttributes($entry) {
|
||||||
|
$retVal = '';
|
||||||
|
|
||||||
|
if(isset($entry['required']) && $entry['required'] === true)
|
||||||
|
$retVal .= ' required';
|
||||||
|
|
||||||
|
if(isset($entry['pattern']))
|
||||||
|
$retVal .= ' pattern="' . $entry['pattern'] . '"';
|
||||||
|
|
||||||
|
if(isset($entry['title']))
|
||||||
|
$retVal .= ' title="' . filter_var($entry['title'], FILTER_SANITIZE_STRING) . '"';
|
||||||
|
|
||||||
|
return $retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getTextInput($entry, $id, $name) {
|
||||||
|
return '<input '
|
||||||
|
. BridgeCard::getInputAttributes($entry)
|
||||||
|
. ' id="'
|
||||||
|
. $id
|
||||||
|
. '" type="text" value="'
|
||||||
|
. filter_var($entry['defaultValue'], FILTER_SANITIZE_STRING)
|
||||||
|
. '" placeholder="'
|
||||||
|
. filter_var($entry['exampleValue'], FILTER_SANITIZE_STRING)
|
||||||
|
. '" name="'
|
||||||
|
. $name
|
||||||
|
. '" /><br>'
|
||||||
|
. PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getNumberInput($entry, $id, $name) {
|
||||||
|
return '<input '
|
||||||
|
. BridgeCard::getInputAttributes($entry)
|
||||||
|
. ' id="'
|
||||||
|
. $id
|
||||||
|
. '" type="number" value="'
|
||||||
|
. filter_var($entry['defaultValue'], FILTER_SANITIZE_NUMBER_INT)
|
||||||
|
. '" placeholder="'
|
||||||
|
. filter_var($entry['exampleValue'], FILTER_SANITIZE_NUMBER_INT)
|
||||||
|
. '" name="'
|
||||||
|
. $name
|
||||||
|
. '" /><br>'
|
||||||
|
. PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getListInput($entry, $id, $name) {
|
||||||
|
$list = '<select '
|
||||||
|
. BridgeCard::getInputAttributes($entry)
|
||||||
|
. ' id="'
|
||||||
|
. $id
|
||||||
|
. '" name="'
|
||||||
|
. $name
|
||||||
|
. '" >';
|
||||||
|
|
||||||
|
foreach($entry['values'] as $name => $value) {
|
||||||
|
if(is_array($value)) {
|
||||||
|
$list .= '<optgroup label="' . htmlentities($name) . '">';
|
||||||
|
foreach($value as $subname => $subvalue) {
|
||||||
|
if($entry['defaultValue'] === $subname
|
||||||
|
|| $entry['defaultValue'] === $subvalue) {
|
||||||
|
$list .= '<option value="'
|
||||||
|
. $subvalue
|
||||||
|
. '" selected>'
|
||||||
|
. $subname
|
||||||
|
. '</option>';
|
||||||
|
} else {
|
||||||
|
$list .= '<option value="'
|
||||||
|
. $subvalue
|
||||||
|
. '">'
|
||||||
|
. $subname
|
||||||
|
. '</option>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$list .= '</optgroup>';
|
||||||
|
} else {
|
||||||
|
if($entry['defaultValue'] === $name
|
||||||
|
|| $entry['defaultValue'] === $value) {
|
||||||
|
$list .= '<option value="'
|
||||||
|
. $value
|
||||||
|
. '" selected>'
|
||||||
|
. $name
|
||||||
|
. '</option>';
|
||||||
|
} else {
|
||||||
|
$list .= '<option value="'
|
||||||
|
. $value
|
||||||
|
. '">'
|
||||||
|
. $name
|
||||||
|
. '</option>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$list .= '</select><br>';
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getCheckboxInput($entry, $id, $name) {
|
||||||
|
return '<input '
|
||||||
|
. BridgeCard::getInputAttributes($entry)
|
||||||
|
. ' id="'
|
||||||
|
. $id
|
||||||
|
. '" type="checkbox" name="'
|
||||||
|
. $name
|
||||||
|
. '" '
|
||||||
|
. ($entry['defaultValue'] === 'checked' ?: '')
|
||||||
|
. ' /><br>'
|
||||||
|
. PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function displayBridgeCard($bridgeName, $formats, $isActive = true){
|
||||||
|
|
||||||
|
$bridge = Bridge::create($bridgeName);
|
||||||
|
|
||||||
|
if($bridge == false)
|
||||||
|
return '';
|
||||||
|
|
||||||
|
$isHttps = strpos($bridge->getURI(), 'https') === 0;
|
||||||
|
|
||||||
|
$uri = $bridge->getURI();
|
||||||
|
$name = $bridge->getName();
|
||||||
|
$description = $bridge->getDescription();
|
||||||
|
$parameters = $bridge->getParameters();
|
||||||
|
|
||||||
|
if(defined('PROXY_URL') && PROXY_BYBRIDGE) {
|
||||||
|
$parameters['global']['_noproxy'] = array(
|
||||||
|
'name' => 'Disable proxy (' . ((defined('PROXY_NAME') && PROXY_NAME) ? PROXY_NAME : PROXY_URL) . ')',
|
||||||
|
'type' => 'checkbox'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(CUSTOM_CACHE_TIMEOUT) {
|
||||||
|
$parameters['global']['_cache_timeout'] = array(
|
||||||
|
'name' => 'Cache timeout in seconds',
|
||||||
|
'type' => 'number',
|
||||||
|
'defaultValue' => $bridge->getCacheTimeout()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$card = <<<CARD
|
||||||
|
<section id="bridge-{$bridgeName}" data-ref="{$bridgeName}">
|
||||||
|
<h2><a href="{$uri}">{$name}</a></h2>
|
||||||
|
<p class="description">{$description}</p>
|
||||||
|
<input type="checkbox" class="showmore-box" id="showmore-{$bridgeName}" />
|
||||||
|
<label class="showmore" for="showmore-{$bridgeName}">Show more</label>
|
||||||
|
CARD;
|
||||||
|
|
||||||
|
// If we don't have any parameter for the bridge, we print a generic form to load it.
|
||||||
|
if(count($parameters) === 0
|
||||||
|
|| count($parameters) === 1 && array_key_exists('global', $parameters)) {
|
||||||
|
|
||||||
|
$card .= BridgeCard::getForm($bridgeName, $formats, $isActive, $isHttps);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
foreach($parameters as $parameterName => $parameter) {
|
||||||
|
if(!is_numeric($parameterName) && $parameterName === 'global')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(array_key_exists('global', $parameters))
|
||||||
|
$parameter = array_merge($parameter, $parameters['global']);
|
||||||
|
|
||||||
|
if(!is_numeric($parameterName))
|
||||||
|
$card .= '<h5>' . $parameterName . '</h5>' . PHP_EOL;
|
||||||
|
|
||||||
|
$card .= BridgeCard::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$card .= '<label class="showless" for="showmore-' . $bridgeName . '">Show less</label>';
|
||||||
|
$card .= '<p class="maintainer">' . $bridge->getMaintainer() . '</p>';
|
||||||
|
$card .= '</section>';
|
||||||
|
|
||||||
|
return $card;
|
||||||
|
}
|
||||||
|
}
|
126
lib/BridgeList.php
Normal file
126
lib/BridgeList.php
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
final class BridgeList {
|
||||||
|
|
||||||
|
private static function getHead() {
|
||||||
|
return <<<EOD
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content="RSS-Bridge" />
|
||||||
|
<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 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</noscript>
|
||||||
|
</head>
|
||||||
|
EOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getBridges($whitelist, $showInactive, &$totalBridges, &$totalActiveBridges) {
|
||||||
|
|
||||||
|
$body = '';
|
||||||
|
$totalActiveBridges = 0;
|
||||||
|
$inactiveBridges = '';
|
||||||
|
|
||||||
|
$bridgeList = Bridge::listBridges();
|
||||||
|
$formats = Format::searchInformation();
|
||||||
|
|
||||||
|
$totalBridges = count($bridgeList);
|
||||||
|
|
||||||
|
foreach($bridgeList as $bridgeName) {
|
||||||
|
|
||||||
|
if(Bridge::isWhitelisted($whitelist, strtolower($bridgeName))) {
|
||||||
|
|
||||||
|
$body .= BridgeCard::displayBridgeCard($bridgeName, $formats);
|
||||||
|
$totalActiveBridges++;
|
||||||
|
|
||||||
|
} elseif($showInactive) {
|
||||||
|
|
||||||
|
// inactive bridges
|
||||||
|
$inactiveBridges .= BridgeCard::displayBridgeCard($bridgeName, $formats, false) . PHP_EOL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$body .= $inactiveBridges;
|
||||||
|
|
||||||
|
return $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getHeader() {
|
||||||
|
$status = '';
|
||||||
|
|
||||||
|
if(defined('DEBUG') && DEBUG === true) {
|
||||||
|
$status .= 'debug mode active';
|
||||||
|
}
|
||||||
|
|
||||||
|
return <<<EOD
|
||||||
|
<header>
|
||||||
|
<h1>RSS-Bridge</h1>
|
||||||
|
<h2>·Reconnecting the Web·</h2>
|
||||||
|
<p class="status">{$status}</p>
|
||||||
|
</header>
|
||||||
|
EOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getSearchbar() {
|
||||||
|
$query = filter_input(INPUT_GET, 'q');
|
||||||
|
|
||||||
|
return <<<EOD
|
||||||
|
<section class="searchbar">
|
||||||
|
<h3>Search</h3>
|
||||||
|
<input type="text" name="searchfield"
|
||||||
|
id="searchfield" placeholder="Enter the bridge you want to search for"
|
||||||
|
onchange="search()" onkeyup="search()" value="{$query}">
|
||||||
|
</section>
|
||||||
|
EOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getFooter($totalBridges, $totalActiveBridges, $showInactive) {
|
||||||
|
$version = Configuration::getVersion();
|
||||||
|
|
||||||
|
$inactive = '';
|
||||||
|
|
||||||
|
if($totalActiveBridges !== $totalBridges) {
|
||||||
|
|
||||||
|
if(!$showInactive) {
|
||||||
|
$inactive = '<a href="?show_inactive=1"><button class="small">Show inactive bridges</button></a><br>';
|
||||||
|
} else {
|
||||||
|
$inactive = '<a href="?show_inactive=0"><button class="small">Hide inactive bridges</button></a><br>';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return <<<EOD
|
||||||
|
<section class="footer">
|
||||||
|
<a href="https://github.com/rss-bridge/rss-bridge">RSS-Bridge ~ Public Domain</a><br>
|
||||||
|
<p class="version">{$version}</p>
|
||||||
|
{$totalActiveBridges}/{$totalBridges} active bridges.<br>
|
||||||
|
{$inactive}
|
||||||
|
</section>
|
||||||
|
EOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function create($whitelist, $showInactive = true) {
|
||||||
|
|
||||||
|
$totalBridges = 0;
|
||||||
|
$totalActiveBridges = 0;
|
||||||
|
|
||||||
|
return '<!DOCTYPE html><html lang="en">'
|
||||||
|
. BridgeList::getHead()
|
||||||
|
. '<body onload="search()">'
|
||||||
|
. BridgeList::getHeader()
|
||||||
|
. BridgeList::getSearchbar()
|
||||||
|
. BridgeList::getBridges($whitelist, $showInactive, $totalBridges, $totalActiveBridges)
|
||||||
|
. BridgeList::getFooter($totalBridges, $totalActiveBridges, $showInactive)
|
||||||
|
. '</body></html>';
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
120
lib/Configuration.php
Normal file
120
lib/Configuration.php
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
class Configuration {
|
||||||
|
|
||||||
|
public static $VERSION = '2018-08-07';
|
||||||
|
|
||||||
|
public static $config = null;
|
||||||
|
|
||||||
|
public static function verifyInstallation() {
|
||||||
|
|
||||||
|
// Check PHP version
|
||||||
|
if(version_compare(PHP_VERSION, PHP_VERSION_REQUIRED) === -1)
|
||||||
|
die('RSS-Bridge requires at least PHP version ' . PHP_VERSION_REQUIRED . '!');
|
||||||
|
|
||||||
|
// extensions check
|
||||||
|
if(!extension_loaded('openssl'))
|
||||||
|
die('"openssl" extension not loaded. Please check "php.ini"');
|
||||||
|
|
||||||
|
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"');
|
||||||
|
|
||||||
|
// 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 . '!');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function loadConfiguration() {
|
||||||
|
|
||||||
|
if(!file_exists('config.default.ini.php'))
|
||||||
|
die('The default configuration file "config.default.ini.php" is missing!');
|
||||||
|
|
||||||
|
Configuration::$config = parse_ini_file('config.default.ini.php', true, INI_SCANNER_TYPED);
|
||||||
|
if(!Configuration::$config)
|
||||||
|
die('Error parsing config.default.ini.php');
|
||||||
|
|
||||||
|
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, Configuration::$config) && array_key_exists($key, Configuration::$config[$header])) {
|
||||||
|
Configuration::$config[$header][$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!is_string(self::getConfig('proxy', 'url')))
|
||||||
|
die('Parameter [proxy] => "url" is not a valid string! Please check "config.ini.php"!');
|
||||||
|
|
||||||
|
if(!empty(self::getConfig('proxy', 'url')))
|
||||||
|
define('PROXY_URL', self::getConfig('proxy', 'url'));
|
||||||
|
|
||||||
|
if(!is_bool(self::getConfig('proxy', 'by_bridge')))
|
||||||
|
die('Parameter [proxy] => "by_bridge" is not a valid Boolean! Please check "config.ini.php"!');
|
||||||
|
|
||||||
|
define('PROXY_BYBRIDGE', self::getConfig('proxy', 'by_bridge'));
|
||||||
|
|
||||||
|
if(!is_string(self::getConfig('proxy', 'name')))
|
||||||
|
die('Parameter [proxy] => "name" is not a valid string! Please check "config.ini.php"!');
|
||||||
|
|
||||||
|
define('PROXY_NAME', self::getConfig('proxy', 'name'));
|
||||||
|
|
||||||
|
if(!is_bool(self::getConfig('cache', 'custom_timeout')))
|
||||||
|
die('Parameter [cache] => "custom_timeout" is not a valid Boolean! Please check "config.ini.php"!');
|
||||||
|
|
||||||
|
define('CUSTOM_CACHE_TIMEOUT', self::getConfig('cache', 'custom_timeout'));
|
||||||
|
|
||||||
|
if(!is_bool(self::getConfig('authentication', 'enable')))
|
||||||
|
die('Parameter [authentication] => "enable" is not a valid Boolean! Please check "config.ini.php"!');
|
||||||
|
|
||||||
|
if(!is_string(self::getConfig('authentication', 'username')))
|
||||||
|
die('Parameter [authentication] => "username" is not a valid string! Please check "config.ini.php"!');
|
||||||
|
|
||||||
|
if(!is_string(self::getConfig('authentication', 'password')))
|
||||||
|
die('Parameter [authentication] => "password" is not a valid string! Please check "config.ini.php"!');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getConfig($category, $key) {
|
||||||
|
|
||||||
|
if(array_key_exists($category, self::$config) && array_key_exists($key, self::$config[$category])) {
|
||||||
|
return self::$config[$category][$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getVersion() {
|
||||||
|
|
||||||
|
$headFile = '.git/HEAD';
|
||||||
|
|
||||||
|
if(file_exists($headFile)) {
|
||||||
|
|
||||||
|
$revisionHashFile = '.git/' . substr(file_get_contents($headFile), 5, -1);
|
||||||
|
$branchName = explode('/', $revisionHashFile)[3];
|
||||||
|
if(file_exists($revisionHashFile)) {
|
||||||
|
return 'git.' . $branchName . '.' . substr(file_get_contents($revisionHashFile), 0, 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Configuration::$VERSION;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -64,7 +64,10 @@ function buildBridgeException($e, $bridge){
|
|||||||
$body = 'Error message: `'
|
$body = 'Error message: `'
|
||||||
. $e->getMessage()
|
. $e->getMessage()
|
||||||
. "`\nQuery string: `"
|
. "`\nQuery string: `"
|
||||||
. $_SERVER['QUERY_STRING'] . '`';
|
. $_SERVER['QUERY_STRING']
|
||||||
|
. "`\nVersion: `"
|
||||||
|
. Configuration::getVersion()
|
||||||
|
. '`';
|
||||||
|
|
||||||
$link = buildGitHubIssueQuery($title, $body, 'bug report', $bridge->getMaintainer());
|
$link = buildGitHubIssueQuery($title, $body, 'bug report', $bridge->getMaintainer());
|
||||||
|
|
||||||
|
@@ -24,15 +24,15 @@ abstract class FeedExpander extends BridgeAbstract {
|
|||||||
switch(true) {
|
switch(true) {
|
||||||
case isset($rssContent->item[0]):
|
case isset($rssContent->item[0]):
|
||||||
debugMessage('Detected RSS 1.0 format');
|
debugMessage('Detected RSS 1.0 format');
|
||||||
$this->feedType = "RSS_1_0";
|
$this->feedType = 'RSS_1_0';
|
||||||
break;
|
break;
|
||||||
case isset($rssContent->channel[0]):
|
case isset($rssContent->channel[0]):
|
||||||
debugMessage('Detected RSS 0.9x or 2.0 format');
|
debugMessage('Detected RSS 0.9x or 2.0 format');
|
||||||
$this->feedType = "RSS_2_0";
|
$this->feedType = 'RSS_2_0';
|
||||||
break;
|
break;
|
||||||
case isset($rssContent->entry[0]):
|
case isset($rssContent->entry[0]):
|
||||||
debugMessage('Detected ATOM format');
|
debugMessage('Detected ATOM format');
|
||||||
$this->feedType = "ATOM_1_0";
|
$this->feedType = 'ATOM_1_0';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
debugMessage('Unknown feed format/version');
|
debugMessage('Unknown feed format/version');
|
||||||
|
@@ -14,6 +14,10 @@ require __DIR__ . '/Bridge.php';
|
|||||||
require __DIR__ . '/BridgeAbstract.php';
|
require __DIR__ . '/BridgeAbstract.php';
|
||||||
require __DIR__ . '/FeedExpander.php';
|
require __DIR__ . '/FeedExpander.php';
|
||||||
require __DIR__ . '/Cache.php';
|
require __DIR__ . '/Cache.php';
|
||||||
|
require __DIR__ . '/Authentication.php';
|
||||||
|
require __DIR__ . '/Configuration.php';
|
||||||
|
require __DIR__ . '/BridgeCard.php';
|
||||||
|
require __DIR__ . '/BridgeList.php';
|
||||||
|
|
||||||
require __DIR__ . '/validation.php';
|
require __DIR__ . '/validation.php';
|
||||||
require __DIR__ . '/html.php';
|
require __DIR__ . '/html.php';
|
||||||
@@ -30,6 +34,17 @@ if(!file_exists($vendorLibSimpleHtmlDom)) {
|
|||||||
}
|
}
|
||||||
require_once $vendorLibSimpleHtmlDom;
|
require_once $vendorLibSimpleHtmlDom;
|
||||||
|
|
||||||
|
$vendorLibPhpUrlJoin = __DIR__ . PATH_VENDOR . '/php-urljoin/src/urljoin.php';
|
||||||
|
if(!file_exists($vendorLibPhpUrlJoin)) {
|
||||||
|
throw new \HttpException('"php-urljoin" library is missing.
|
||||||
|
Get it from https://github.com/fluffy-critter/php-urljoin and place the script "urljoin.php" in '
|
||||||
|
. substr(PATH_VENDOR, 4)
|
||||||
|
. '/php-urljoin/src/',
|
||||||
|
500);
|
||||||
|
}
|
||||||
|
require_once $vendorLibPhpUrlJoin;
|
||||||
|
|
||||||
|
|
||||||
/* Example use
|
/* Example use
|
||||||
|
|
||||||
require_once __DIR__ . '/lib/RssBridge.php';
|
require_once __DIR__ . '/lib/RssBridge.php';
|
||||||
|
@@ -21,13 +21,34 @@ function getContents($url, $header = array(), $opts = array()){
|
|||||||
curl_setopt($ch, CURLOPT_PROXY, PROXY_URL);
|
curl_setopt($ch, CURLOPT_PROXY, PROXY_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
$content = curl_exec($ch);
|
// We always want the resonse header as part of the data!
|
||||||
|
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||||
|
|
||||||
|
$data = curl_exec($ch);
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
$curlErrno = curl_errno($ch);
|
||||||
|
|
||||||
|
if($data === false)
|
||||||
|
debugMessage('Cant\'t download ' . $url . ' cUrl error: ' . $curlError . ' (' . $curlErrno . ')');
|
||||||
|
|
||||||
|
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||||
|
$header = substr($data, 0, $headerSize);
|
||||||
|
$headers = parseResponseHeader($header);
|
||||||
|
$finalHeader = end($headers);
|
||||||
|
|
||||||
|
if(array_key_exists('http_code', $finalHeader)
|
||||||
|
&& strpos($finalHeader['http_code'], '200') === false
|
||||||
|
&& array_key_exists('Server', $finalHeader)
|
||||||
|
&& strpos($finalHeader['Server'], 'cloudflare') !== false) {
|
||||||
|
returnServerError(<<< EOD
|
||||||
|
The server responded with a Cloudflare challenge, which is not supported by RSS-Bridge!<br>
|
||||||
|
If this error persists longer than a week, please consider opening an issue on GitHub!
|
||||||
|
EOD
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
return substr($data, $headerSize);
|
||||||
if($content === false)
|
|
||||||
debugMessage('Cant\'t download ' . $url);
|
|
||||||
|
|
||||||
return $content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSimpleHTMLDOM($url,
|
function getSimpleHTMLDOM($url,
|
||||||
@@ -96,3 +117,38 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
|
|||||||
$defaultBRText,
|
$defaultBRText,
|
||||||
$defaultSpanText);
|
$defaultSpanText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the provided response header into an associative array
|
||||||
|
*
|
||||||
|
* Based on https://stackoverflow.com/a/18682872
|
||||||
|
*/
|
||||||
|
function parseResponseHeader($header) {
|
||||||
|
|
||||||
|
$headers = array();
|
||||||
|
$requests = explode("\r\n\r\n", trim($header));
|
||||||
|
|
||||||
|
foreach ($requests as $request) {
|
||||||
|
|
||||||
|
$header = array();
|
||||||
|
|
||||||
|
foreach (explode("\r\n", $request) as $i => $line) {
|
||||||
|
|
||||||
|
if($i === 0) {
|
||||||
|
$header['http_code'] = $line;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
list ($key, $value) = explode(': ', $line);
|
||||||
|
$header[$key] = $value;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers[] = $header;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $headers;
|
||||||
|
|
||||||
|
}
|
||||||
|
311
lib/html.php
311
lib/html.php
@@ -1,304 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
function displayBridgeCard($bridgeName, $formats, $isActive = true){
|
|
||||||
|
|
||||||
$getHelperButtonsFormat = function($formats){
|
|
||||||
$buttons = '';
|
|
||||||
foreach($formats as $name) {
|
|
||||||
$buttons .= '<button type="submit" name="format" value="'
|
|
||||||
. $name
|
|
||||||
. '">'
|
|
||||||
. $name
|
|
||||||
. '</button>'
|
|
||||||
. PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $buttons;
|
|
||||||
};
|
|
||||||
|
|
||||||
$getFormHeader = function($bridgeName){
|
|
||||||
return <<<EOD
|
|
||||||
<form method="GET" action="?">
|
|
||||||
<input type="hidden" name="action" value="display" />
|
|
||||||
<input type="hidden" name="bridge" value="{$bridgeName}" />
|
|
||||||
EOD;
|
|
||||||
};
|
|
||||||
|
|
||||||
$bridge = Bridge::create($bridgeName);
|
|
||||||
|
|
||||||
if($bridge == false)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
$HTTPSWarning = '';
|
|
||||||
if(strpos($bridge->getURI(), 'https') !== 0) {
|
|
||||||
|
|
||||||
$HTTPSWarning = '<div class="secure-warning">Warning :
|
|
||||||
This bridge is not fetching its content through a secure connection</div>';
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = '<a href="' . $bridge->getURI() . '">' . $bridge->getName() . '</a>';
|
|
||||||
$description = $bridge->getDescription();
|
|
||||||
|
|
||||||
$card = <<<CARD
|
|
||||||
<section id="bridge-{$bridgeName}" data-ref="{$bridgeName}">
|
|
||||||
<h2>{$name}</h2>
|
|
||||||
<p class="description">
|
|
||||||
{$description}
|
|
||||||
</p>
|
|
||||||
<input type="checkbox" class="showmore-box" id="showmore-{$bridgeName}" />
|
|
||||||
<label class="showmore" for="showmore-{$bridgeName}">Show more</label>
|
|
||||||
CARD;
|
|
||||||
|
|
||||||
// If we don't have any parameter for the bridge, we print a generic form to load it.
|
|
||||||
if(count($bridge->getParameters()) == 0) {
|
|
||||||
|
|
||||||
$card .= $getFormHeader($bridgeName);
|
|
||||||
$card .= $HTTPSWarning;
|
|
||||||
|
|
||||||
if($isActive) {
|
|
||||||
if(defined('PROXY_URL') && PROXY_BYBRIDGE) {
|
|
||||||
$idArg = 'arg-'
|
|
||||||
. urlencode($bridgeName)
|
|
||||||
. '-'
|
|
||||||
. urlencode('proxyoff')
|
|
||||||
. '-'
|
|
||||||
. urlencode('_noproxy');
|
|
||||||
|
|
||||||
$card .= '<input id="'
|
|
||||||
. $idArg
|
|
||||||
. '" type="checkbox" name="_noproxy" />'
|
|
||||||
. PHP_EOL;
|
|
||||||
|
|
||||||
$card .= '<label for="'
|
|
||||||
. $idArg
|
|
||||||
. '">Disable proxy ('
|
|
||||||
. ((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>';
|
|
||||||
}
|
|
||||||
|
|
||||||
$card .= '</form>' . PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
$hasGlobalParameter = array_key_exists('global', $bridge->getParameters());
|
|
||||||
|
|
||||||
if($hasGlobalParameter) {
|
|
||||||
$globalParameters = $bridge->getParameters()['global'];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach($bridge->getParameters() as $parameterName => $parameter) {
|
|
||||||
if(!is_numeric($parameterName) && $parameterName == 'global')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if($hasGlobalParameter)
|
|
||||||
$parameter = array_merge($parameter, $globalParameters);
|
|
||||||
|
|
||||||
if(!is_numeric($parameterName))
|
|
||||||
$card .= '<h5>' . $parameterName . '</h5>' . PHP_EOL;
|
|
||||||
|
|
||||||
$card .= $getFormHeader($bridgeName);
|
|
||||||
$card .= $HTTPSWarning;
|
|
||||||
|
|
||||||
foreach($parameter as $id => $inputEntry) {
|
|
||||||
$additionalInfoString = '';
|
|
||||||
|
|
||||||
if(isset($inputEntry['required']) && $inputEntry['required'] === true)
|
|
||||||
$additionalInfoString .= ' required';
|
|
||||||
|
|
||||||
if(isset($inputEntry['pattern']))
|
|
||||||
$additionalInfoString .= ' pattern="' . $inputEntry['pattern'] . '"';
|
|
||||||
|
|
||||||
if(isset($inputEntry['title']))
|
|
||||||
$additionalInfoString .= ' title="' . $inputEntry['title'] . '"';
|
|
||||||
|
|
||||||
if(!isset($inputEntry['exampleValue']))
|
|
||||||
$inputEntry['exampleValue'] = '';
|
|
||||||
|
|
||||||
if(!isset($inputEntry['defaultValue']))
|
|
||||||
$inputEntry['defaultValue'] = '';
|
|
||||||
|
|
||||||
$idArg = 'arg-'
|
|
||||||
. urlencode($bridgeName)
|
|
||||||
. '-'
|
|
||||||
. urlencode($parameterName)
|
|
||||||
. '-'
|
|
||||||
. urlencode($id);
|
|
||||||
|
|
||||||
$card .= '<label for="'
|
|
||||||
. $idArg
|
|
||||||
. '">'
|
|
||||||
. $inputEntry['name']
|
|
||||||
. ' : </label>'
|
|
||||||
. PHP_EOL;
|
|
||||||
|
|
||||||
if(!isset($inputEntry['type']) || $inputEntry['type'] == 'text') {
|
|
||||||
$card .= '<input '
|
|
||||||
. $additionalInfoString
|
|
||||||
. ' id="'
|
|
||||||
. $idArg
|
|
||||||
. '" type="text" value="'
|
|
||||||
. $inputEntry['defaultValue']
|
|
||||||
. '" placeholder="'
|
|
||||||
. $inputEntry['exampleValue']
|
|
||||||
. '" name="'
|
|
||||||
. $id
|
|
||||||
. '" /><br />'
|
|
||||||
. PHP_EOL;
|
|
||||||
} elseif($inputEntry['type'] == 'number') {
|
|
||||||
$card .= '<input '
|
|
||||||
. $additionalInfoString
|
|
||||||
. ' id="'
|
|
||||||
. $idArg
|
|
||||||
. '" type="number" value="'
|
|
||||||
. $inputEntry['defaultValue']
|
|
||||||
. '" placeholder="'
|
|
||||||
. $inputEntry['exampleValue']
|
|
||||||
. '" name="'
|
|
||||||
. $id
|
|
||||||
. '" /><br />'
|
|
||||||
. PHP_EOL;
|
|
||||||
} else if($inputEntry['type'] == 'list') {
|
|
||||||
$card .= '<select '
|
|
||||||
. $additionalInfoString
|
|
||||||
. ' id="'
|
|
||||||
. $idArg
|
|
||||||
. '" name="'
|
|
||||||
. $id
|
|
||||||
. '" >';
|
|
||||||
|
|
||||||
foreach($inputEntry['values'] as $name => $value) {
|
|
||||||
if(is_array($value)) {
|
|
||||||
$card .= '<optgroup label="' . htmlentities($name) . '">';
|
|
||||||
foreach($value as $subname => $subvalue) {
|
|
||||||
if($inputEntry['defaultValue'] === $subname
|
|
||||||
|| $inputEntry['defaultValue'] === $subvalue) {
|
|
||||||
$card .= '<option value="'
|
|
||||||
. $subvalue
|
|
||||||
. '" selected>'
|
|
||||||
. $subname
|
|
||||||
. '</option>';
|
|
||||||
} else {
|
|
||||||
$card .= '<option value="'
|
|
||||||
. $subvalue
|
|
||||||
. '">'
|
|
||||||
. $subname
|
|
||||||
. '</option>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$card .= '</optgroup>';
|
|
||||||
} else {
|
|
||||||
if($inputEntry['defaultValue'] === $name
|
|
||||||
|| $inputEntry['defaultValue'] === $value) {
|
|
||||||
$card .= '<option value="'
|
|
||||||
. $value
|
|
||||||
. '" selected>'
|
|
||||||
. $name
|
|
||||||
. '</option>';
|
|
||||||
} else {
|
|
||||||
$card .= '<option value="'
|
|
||||||
. $value
|
|
||||||
. '">'
|
|
||||||
. $name
|
|
||||||
. '</option>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$card .= '</select><br >';
|
|
||||||
} elseif($inputEntry['type'] == 'checkbox') {
|
|
||||||
if($inputEntry['defaultValue'] === 'checked')
|
|
||||||
$card .= '<input '
|
|
||||||
. $additionalInfoString
|
|
||||||
. ' id="'
|
|
||||||
. $idArg
|
|
||||||
. '" type="checkbox" name="'
|
|
||||||
. $id
|
|
||||||
. '" checked /><br />'
|
|
||||||
. PHP_EOL;
|
|
||||||
else
|
|
||||||
$card .= '<input '
|
|
||||||
. $additionalInfoString
|
|
||||||
. ' id="'
|
|
||||||
. $idArg
|
|
||||||
. '" type="checkbox" name="'
|
|
||||||
. $id
|
|
||||||
. '" /><br />'
|
|
||||||
. PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if($isActive) {
|
|
||||||
if(defined('PROXY_URL') && PROXY_BYBRIDGE) {
|
|
||||||
$idArg = 'arg-'
|
|
||||||
. urlencode($bridgeName)
|
|
||||||
. '-'
|
|
||||||
. urlencode('proxyoff')
|
|
||||||
. '-'
|
|
||||||
. urlencode('_noproxy');
|
|
||||||
|
|
||||||
$card .= '<input id="'
|
|
||||||
. $idArg
|
|
||||||
. '" type="checkbox" name="_noproxy" />'
|
|
||||||
. PHP_EOL;
|
|
||||||
|
|
||||||
$card .= '<label for="'
|
|
||||||
. $idArg
|
|
||||||
. '">Disable proxy ('
|
|
||||||
. ((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>';
|
|
||||||
}
|
|
||||||
$card .= '</form>' . PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
$card .= '<label class="showless" for="showmore-' . $bridgeName . '">Show less</label>';
|
|
||||||
$card .= '<p class="maintainer">' . $bridge->getMaintainer() . '</p>';
|
|
||||||
$card .= '</section>';
|
|
||||||
|
|
||||||
return $card;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitize($textToSanitize,
|
function sanitize($textToSanitize,
|
||||||
$removedTags = array('script', 'iframe', 'input', 'form'),
|
$removedTags = array('script', 'iframe', 'input', 'form'),
|
||||||
$keptAttributes = array('title', 'href', 'src'),
|
$keptAttributes = array('title', 'href', 'src'),
|
||||||
@@ -342,18 +42,11 @@ function backgroundToImg($htmlContent) {
|
|||||||
|
|
||||||
function defaultLinkTo($content, $server){
|
function defaultLinkTo($content, $server){
|
||||||
foreach($content->find('img') as $image) {
|
foreach($content->find('img') as $image) {
|
||||||
if(strpos($image->src, 'http') === false
|
$image->src = urljoin($server, $image->src);
|
||||||
&& strpos($image->src, '//') === false
|
|
||||||
&& strpos($image->src, 'data:') === false)
|
|
||||||
$image->src = $server . $image->src;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($content->find('a') as $anchor) {
|
foreach($content->find('a') as $anchor) {
|
||||||
if(strpos($anchor->href, 'http') === false
|
$anchor->href = urljoin($server, $anchor->href);
|
||||||
&& strpos($anchor->href, '//') === false
|
|
||||||
&& strpos($anchor->href, '#') !== 0
|
|
||||||
&& strpos($anchor->href, '?') !== 0)
|
|
||||||
$anchor->href = $server . $anchor->href;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
|
@@ -70,4 +70,8 @@
|
|||||||
<rule ref="Squiz.WhiteSpace.SemicolonSpacing"/>
|
<rule ref="Squiz.WhiteSpace.SemicolonSpacing"/>
|
||||||
<!-- Do not add whitespace at start or end of a file or end of a line -->
|
<!-- Do not add whitespace at start or end of a file or end of a line -->
|
||||||
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"/>
|
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"/>
|
||||||
|
<!-- Whenever possible use single quote strings -->
|
||||||
|
<rule ref="Squiz.Strings.DoubleQuoteUsage">
|
||||||
|
<exclude name="Squiz.Strings.DoubleQuoteUsage.ContainsVar" />
|
||||||
|
</rule>
|
||||||
</ruleset>
|
</ruleset>
|
||||||
|
@@ -5,7 +5,6 @@ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockq
|
|||||||
border: 0;
|
border: 0;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
font: inherit;
|
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -70,12 +69,14 @@ a.backlink, a.backlink:link, a.backlink:visited, a.itemtitle, a.itemtitle:link,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section > div.content, section > div.categories,
|
||||||
section > div.content, section > div.attachments {
|
section > div.content, section > div.attachments {
|
||||||
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section > div.categories > li.category,
|
||||||
section > div.attachments > li.enclosure {
|
section > div.attachments > li.enclosure {
|
||||||
|
|
||||||
list-style-type: circle;
|
list-style-type: circle;
|
||||||
@@ -111,3 +112,8 @@ button.backbutton, button.rss-feed {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
@@ -3,20 +3,53 @@ function search() {
|
|||||||
var searchTerm = document.getElementById('searchfield').value;
|
var searchTerm = document.getElementById('searchfield').value;
|
||||||
var searchableElements = document.getElementsByTagName('section');
|
var searchableElements = document.getElementsByTagName('section');
|
||||||
|
|
||||||
var regexMatch = new RegExp(searchTerm, "i");
|
var regexMatch = new RegExp(searchTerm, 'i');
|
||||||
|
|
||||||
|
// Attempt to create anchor from search term (will default to 'localhost' on failure)
|
||||||
|
var searchTermUri = document.createElement('a');
|
||||||
|
searchTermUri.href = searchTerm;
|
||||||
|
|
||||||
|
if(searchTermUri.hostname == 'localhost') {
|
||||||
|
searchTermUri = null;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Ignore "www."
|
||||||
|
if(searchTermUri.hostname.indexOf('www.') === 0) {
|
||||||
|
searchTermUri.hostname = searchTermUri.hostname.substr(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
for(var i = 0; i < searchableElements.length; i++) {
|
for(var i = 0; i < searchableElements.length; i++) {
|
||||||
|
|
||||||
var textValue = searchableElements[i].getAttribute('data-ref');
|
var textValue = searchableElements[i].getAttribute('data-ref');
|
||||||
if(textValue != null) {
|
var anchors = searchableElements[i].getElementsByTagName('a');
|
||||||
|
|
||||||
if(textValue.match(regexMatch) == null && searchableElements[i].style.display != "none") {
|
if(anchors != null && anchors.length > 0) {
|
||||||
|
|
||||||
searchableElements[i].style.display = "none";
|
var uriValue = anchors[0]; // First anchor is bridge URI
|
||||||
|
|
||||||
} else if(textValue.match(regexMatch) != null) {
|
// Ignore "www."
|
||||||
|
if(uriValue.hostname.indexOf('www.') === 0) {
|
||||||
|
uriValue.hostname = uriValue.hostname.substr(4);
|
||||||
|
}
|
||||||
|
|
||||||
searchableElements[i].style.display = "block";
|
}
|
||||||
|
|
||||||
|
if(textValue != null || uriValue != null) {
|
||||||
|
|
||||||
|
if(textValue.match(regexMatch) != null ||
|
||||||
|
uriValue.hostname.match(regexMatch) ||
|
||||||
|
searchTermUri != null &&
|
||||||
|
uriValue.hostname != 'localhost' && (
|
||||||
|
uriValue.href.match(regexMatch) != null ||
|
||||||
|
uriValue.hostname == searchTermUri.hostname)) {
|
||||||
|
|
||||||
|
searchableElements[i].style.display = 'block';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
searchableElements[i].style.display = 'none';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -143,6 +143,11 @@ section.footer:hover {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section.footer .version {
|
||||||
|
|
||||||
|
font-size: 80%;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
section > h2 {
|
section > h2 {
|
||||||
|
|
||||||
|
131
vendor/php-urljoin/src/urljoin.php
vendored
Normal file
131
vendor/php-urljoin/src/urljoin.php
vendored
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
A spiritual port of Python's urlparse.urljoin() function to PHP. Why this isn't in the standard library is anyone's guess.
|
||||||
|
|
||||||
|
Author: fluffy, http://beesbuzz.biz/
|
||||||
|
Latest version at: https://github.com/plaidfluff/php-urljoin
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
function urljoin($base, $rel) {
|
||||||
|
if (!$base) {
|
||||||
|
return $rel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$rel) {
|
||||||
|
return $base;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uses_relative = array('', 'ftp', 'http', 'gopher', 'nntp', 'imap',
|
||||||
|
'wais', 'file', 'https', 'shttp', 'mms',
|
||||||
|
'prospero', 'rtsp', 'rtspu', 'sftp',
|
||||||
|
'svn', 'svn+ssh', 'ws', 'wss');
|
||||||
|
|
||||||
|
$pbase = parse_url($base);
|
||||||
|
$prel = parse_url($rel);
|
||||||
|
|
||||||
|
if (array_key_exists('path', $pbase) && $pbase['path'] === '/') {
|
||||||
|
unset($pbase['path']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($prel['scheme'])) {
|
||||||
|
if ($prel['scheme'] != $pbase['scheme'] || in_array($prel['scheme'], $uses_relative) == false) {
|
||||||
|
return $rel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$merged = array_merge($pbase, $prel);
|
||||||
|
|
||||||
|
// Handle relative paths:
|
||||||
|
// 'path/to/file.ext'
|
||||||
|
// './path/to/file.ext'
|
||||||
|
if (array_key_exists('path', $prel) && substr($prel['path'], 0, 1) != '/') {
|
||||||
|
|
||||||
|
// Normalize: './path/to/file.ext' => 'path/to/file.ext'
|
||||||
|
if (substr($prel['path'], 0, 2) === './') {
|
||||||
|
$prel['path'] = substr($prel['path'], 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('path', $pbase)) {
|
||||||
|
$dir = preg_replace('@/[^/]*$@', '', $pbase['path']);
|
||||||
|
$merged['path'] = $dir . '/' . $prel['path'];
|
||||||
|
} else {
|
||||||
|
$merged['path'] = '/' . $prel['path'];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(array_key_exists('path', $merged)) {
|
||||||
|
// Get the path components, and remove the initial empty one
|
||||||
|
$pathParts = explode('/', $merged['path']);
|
||||||
|
array_shift($pathParts);
|
||||||
|
|
||||||
|
$path = [];
|
||||||
|
$prevPart = '';
|
||||||
|
foreach ($pathParts as $part) {
|
||||||
|
if ($part == '..' && count($path) > 0) {
|
||||||
|
// Cancel out the parent directory (if there's a parent to cancel)
|
||||||
|
$parent = array_pop($path);
|
||||||
|
// But if it was also a parent directory, leave it in
|
||||||
|
if ($parent == '..') {
|
||||||
|
array_push($path, $parent);
|
||||||
|
array_push($path, $part);
|
||||||
|
}
|
||||||
|
} else if ($prevPart != '' || ($part != '.' && $part != '')) {
|
||||||
|
// Don't include empty or current-directory components
|
||||||
|
if ($part == '.') {
|
||||||
|
$part = '';
|
||||||
|
}
|
||||||
|
array_push($path, $part);
|
||||||
|
}
|
||||||
|
$prevPart = $part;
|
||||||
|
}
|
||||||
|
$merged['path'] = '/' . implode('/', $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ret = '';
|
||||||
|
if (isset($merged['scheme'])) {
|
||||||
|
$ret .= $merged['scheme'] . ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($merged['scheme']) || isset($merged['host'])) {
|
||||||
|
$ret .= '//';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($prel['host'])) {
|
||||||
|
$hostSource = $prel;
|
||||||
|
} else {
|
||||||
|
$hostSource = $pbase;
|
||||||
|
}
|
||||||
|
|
||||||
|
// username, password, and port are associated with the hostname, not merged
|
||||||
|
if (isset($hostSource['host'])) {
|
||||||
|
if (isset($hostSource['user'])) {
|
||||||
|
$ret .= $hostSource['user'];
|
||||||
|
if (isset($hostSource['pass'])) {
|
||||||
|
$ret .= ':' . $hostSource['pass'];
|
||||||
|
}
|
||||||
|
$ret .= '@';
|
||||||
|
}
|
||||||
|
$ret .= $hostSource['host'];
|
||||||
|
if (isset($hostSource['port'])) {
|
||||||
|
$ret .= ':' . $hostSource['port'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($merged['path'])) {
|
||||||
|
$ret .= $merged['path'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($prel['query'])) {
|
||||||
|
$ret .= '?' . $prel['query'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($prel['fragment'])) {
|
||||||
|
$ret .= '#' . $prel['fragment'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
Reference in New Issue
Block a user