1
0
mirror of https://github.com/RSS-Bridge/rss-bridge.git synced 2025-08-25 17:31:25 +02:00

Compare commits

..

44 Commits

Author SHA1 Message Date
teromene
b0c7a62f74 [index] Bumped version to 2018-04-20 2018-04-20 17:15:25 +02:00
teromene
57b15a089e Added DiscogsBridge. Closes #615 2018-04-20 16:57:09 +02:00
teromene
4b7fbe4188 DansTonChatBridge: test before accessing plaintext 2018-04-19 21:00:18 +02:00
Teromene
2390fb58b3 Merge pull request #673 from GregThib/patch-1
DansTonChatBridge: Update to follow DTC website changes
2018-04-19 20:58:01 +02:00
Teromene
1e8d29f6ec Merge pull request #672 from em92/patch-3
[YoutubeBridge] Removed duration in titles on search mode
2018-04-19 20:56:34 +02:00
Eugene Molotov
644d13686c [YoutubeBridge] Removed duration in titles on search mode 2018-04-19 09:03:29 +05:00
teromene
aa0ff1c9b1 Added YGGTorrentBridge. 2018-04-18 21:57:27 +02:00
teromene
539d9f1f06 Add SupInfoBridge, fixes #668 2018-04-18 12:39:45 +02:00
teromene
5ece801ce7 Fix h* display size in HtmlFormat, and fix images being wider than the page. 2018-04-18 12:29:22 +02:00
GregThib
4dcea6d9c9 Update to follow DTC website changes
Now, entry title is optionnal and may be found in h3 HTML element.
Entry content is mandatory and may be found in div[class="item-content"] HTML element.

Moreover, the title may contain simple quotes (here, encoded) so the bridge have to decode first to apply format library function. In case we don't do that, the format function double encode the quote and something like ' could appear.
2018-04-18 12:00:00 +02:00
teromene
d69e2521f1 Removed T411 bridge. Website was closed nearly one year ago. 2018-04-18 11:44:54 +02:00
teromene
7927d73719 Rewrote DemonoidBridge. Fixes #626. 2018-04-17 15:25:02 +02:00
teromene
0620f30ae0 Changed the API key used for SoundCloud bridge. Should fix #599 2018-04-17 14:24:00 +02:00
teromene
795494cfce Added enclosures to InstagramBridge. 2018-04-16 19:34:21 +02:00
teromene
ba8542156c Remove usage of function file_get_contents. 2018-04-16 19:27:20 +02:00
Eugene Molotov
55f112e034 [VkBridge] Rewrited bridge code (#667)
* [VkBridge] Convert special HTML entities to characters in pageName

* [VkBridge] Generate feed item title

* [VkBridge] Remove double backslashes in feed item link

* [VkBridge] Unpin post if pinned

* [VkBridge] Mark reposted messages

* [VkBridge] Correct external link parsing

* [VkBridge] Added article parsing

* [VkBridge] Added video parsing

* [VkBridge] Added photo parsing

* [VkBridge] Added album link parsing

* [VkBridge] Added one more external link selector

* [VkBridge] Using array of link selectors to remove

* [VkBridge] Added document parsing

* [VkBridge] Added sign parsing

* [VkBridge] Fixed incorrect sorting with pinned item

* [VkBridge] More methods to parse documents

* [VkBridge] Save fallback if page name element not found

* [VkBridge] Using post signed as feed item author

* [VkBridge] Fixed document link

* [VkBridge] Coding policy fixes
2018-04-16 10:55:31 +01:00
Mitsukarenai
208fff801d [FDroid] minor fixes for Travis CI 2018-04-15 13:21:48 +02:00
Mitsukarenai
3c9860de43 [FDroid] new bridge 2018-04-15 13:13:10 +02:00
Adam Tygart
a16ec196c5 [NotAlways] Add a bridge for the NotAlways family of sites (#537)
NotAlways right found it necessary to remove their RSS feeds recently. This is a *simple* bridge to grab the ones on the front page. It allows you to filter the articles based on their classification (right, working, romantic, related, learning, friendly, hopeless, unfiltered, or all).
2018-04-15 12:02:37 +01:00
teromene
887fc7b037 Fix GoComics, website completely changed. Fixes #663 2018-04-14 18:15:44 +01:00
teromene
1bd4a40f71 Added GNOME Builder configuration to gitignore. 2018-04-14 18:15:13 +01:00
teromene
494169f959 Added bridge for Pixiv.
This bridge is slow, as caching of images is required (REFERER header required to access the full size images)
2018-04-14 16:19:35 +01:00
logmanoriginal
178177e787 [index] Push version to 2018-04-06 2018-04-06 22:45:33 +02:00
logmanoriginal
1cb83ccea3 [IPBBridge] Use limit for the number of items
The limit was used to specify the number of pages to return from a given
topic which resulted in the number of returned items variing between one
and however many entries are listed on one page.

This commit changes the implementation for the limit to keep loading more
pages until the specified limit is reached. Excessive elements are removed
in order to return the exact amount of items specified by the limit.

This behavior is closer to how other bridges are implemented and makes it
more natural to use without being too confusing. Existing queries must be
updated to account for the new limit.

References #657
2018-04-06 22:25:49 +02:00
sysadminstory
c899399569 [DealabsBridge] Follow the website changes (#660) 2018-04-06 21:25:41 +02:00
LogMANOriginal
0f93370e92 Merge pull request #654 from LogMANOriginal/cURL
Use cURL instead of file_get_contents
2018-04-06 20:49:58 +02:00
logmanoriginal
45c3dcb636 [VkBridge] Simplify header specification 2018-04-06 20:42:19 +02:00
logmanoriginal
ecfc220b10 [KernelBugTrackerBridge] Fix too many parameters requesting HTML DOM 2018-04-06 20:42:19 +02:00
logmanoriginal
4b3efed7ec [YoutubeBridge] Fix too many parameters when using HTML mode 2018-04-06 20:42:19 +02:00
logmanoriginal
bc28c5da8e [contents] Set CURLOPT_HTTPHEADER only if the provided array contains data 2018-04-06 20:42:19 +02:00
logmanoriginal
5bd9c1611d [contents] Limit cURL protocols to HTTP and HTTPS 2018-04-06 20:42:19 +02:00
logmanoriginal
6caca4946b bridges: Fix bridges with custom headers and options
This commit fixes bridges which called getContents, getSimpleHTMLDOM
or getSimpleHTMLDOMCached with custom settings.
2018-04-06 20:42:19 +02:00
logmanoriginal
ee78e7613f [contents] Replace file_get_contents by cURL
cURL is a powerful library specifically designed to connect to many
different types of servers with different types of protocols. For
more detailed information refer to the PHP cURL manual:

- http://php.net/manual/en/book.curl.php

Due to this change some parameters for the getContents function were
necessary (also applies to getSimpleHTMLDOM and getSimpleHTMLDOMCached):

> $use_include_path removed

  This parameter has never been used and doesn't even make sense in
  this context; If set to true file_get_contents would also search
  for files in the include_path (specified in php.ini).

> $context replaced by $header and $opts

  The $context parameter allowed for customization of the request in
  order to change how file_get_contents would acquire the data (i.e.
  using POST instead of GET, sending custom header, etc...)

  cURL also provides facilities to specify custom headers and change
  how it communicates to severs. cURL, however, is much more advanced.

  - $header is an optional parameter (empty by default). It receives
    an array of strings to send in the HTTP request header.

    See 'CURLOPT_HTTPHEADER':

    "An array of HTTP header fields to set, in the format
    array('Content-type: text/plain', 'Content-length: 100')"

    - php.net/manual/en/function.curl-setopt.php

  - $opts is an optional parameter (empty by default). It receives
    an array of options, where each option is a key-value-pair of
    a cURL option (CURLOPT_*) and it's associated parameter. This
    parameter accepts any of the CURLOPT_* settings.

    Example (sending POST instead of GET):

    $opts = array(
      CURLOPT_POST => 1,
      CURLOPT_POSTFIELDS => '&action=none'
    );
    $html = getContents($url, array(), $opts);

    Refer to the cURL setopt manual for more information:
    - php.net/manual/en/function.curl-setopt.php

> $offset and $maxlen removed

  These options were supported by file_get_contents, but there doesn't
  seem to be an equivalent in cURL. Since no caller uses them they are
  safe to remove.

Compressed data / Encoding

  By using cURL instead of file_get_contents RSS-Bridge no longer has
  to handle compressed data manually.

  See 'CURLOPT_ENCODING':

  "[...] Supported encodings are "identity", "deflate", and "gzip".
  If an empty string, "", is set, a header containing all supported
  encoding types is sent."

  - http://php.net/manual/en/function.curl-setopt.php

  Notice: By default all encoding types are accepted (""). This can
  be changed by setting a custom option via $opts.

    Example:

    $opts = array(CURLOPT_ENCODING => 'gzip');
    $html = getContents($url, array(), $opts);

Proxy

The proxy implementation should still work, but there doesn't seem
to be an equivalent for 'request_fulluri = true'. To my understanding
this isn't an issue because cURL knows how to handle proxy communication.
2018-04-06 20:42:19 +02:00
logmanoriginal
2df2623430 [index] Add 'curl' extension check 2018-04-06 20:42:19 +02:00
logmanoriginal
de5f850cdb [index] Fix indentation using tabs 2018-04-06 20:34:44 +02:00
teromene
ac6847045c Catch Errors in order to display a message in more cases. We also catch Exceptions to maintain compat with php 5.
Add check for simplexml extension.
2018-04-04 19:02:40 +01:00
logmanoriginal
df6da837dc [FacebookBridge] Return error if username starts with slash
Requesting a username with a leading slash would cause error 500
because the requested URI would contain two slashes in a row.

For example username "/test" would result in:
https://facebook.com//test

References #628
2018-03-23 21:23:30 +01:00
Eugene Molotov
41b7984a4e [YoutubeBridge] Playlist mode: faster feed generating if item count is less or equal to 15 (#648)
* [YoutubeBridge] Playlist mode: faster feed generating if item count is less or equal to 15
2018-03-19 12:41:52 +00:00
teromene
38c7e0272e Add hashtag support to InstagramBridge.
Fixes  #629
2018-03-19 12:29:24 +00:00
teromene
29c690dbcd Fix InstagramBridge, thanks to @pintassilgo comments.
Fixes #646
2018-03-19 12:17:42 +00:00
LogMANOriginal
8ba817478b Implement customizable cache timeout (#641)
* [BridgeAbstract] Implement customizable cache timeout

The customizable cache timeout is used instead of the default cache
timeout (CACHE_TIMEOUT) if specified by the caller.

* [index] Add new global parameter '_cache_timeout'

The _cache_timeout parameter is an optional parameter that can be
used to specify a custom cache timeout. This option is enabled by
default.

It can be disabled using the named constant 'CUSTOM_CACHE_TIMEOUT'
which supports two states:

> true: Enabled (default)
> false: Disabled

* [BridgeAbstract] Change scope of 'getCacheTimeout' to public

* [html] Add cache timeout parameter to all bridges

The timeout parameter only shows if CUSTOM_CACHE_TIMEOUT has been set
to true. The default value is automatically set to the value specified
in the bridge.

* [index] Disable custom cache timeout by default
2018-03-14 18:06:36 +01:00
Eugene Molotov
cacbe90102 [YoutubeBridge] Sort playlist items by publication date (#643) 2018-03-13 11:24:40 +00:00
Antoine Cadoret
cb91cd5d2f Fix SteamBridge (#637) (#639)
Fixes #639
2018-03-12 09:22:34 +00:00
sysadminstory
52dfa3fe76 [RadioMelodieBridge] Add new bridge (#640) 2018-03-11 15:38:07 +01:00
29 changed files with 1198 additions and 454 deletions

5
.gitignore vendored
View File

@@ -231,4 +231,7 @@ DEBUG
######################
## VisualStudioCode ##
######################
.vscode/*
.vscode/*
#Builder
.buildconfig

View File

@@ -64,13 +64,11 @@ class Arte7Bridge extends BridgeAbstract {
. $lang
. ($category != null ? '&category.code=' . $category : '');
$context = array(
'http' => array(
'header' => 'Authorization: Bearer '. self::API_TOKEN
)
$header = array(
'Authorization: Bearer ' . self::API_TOKEN
);
$input = getContents($url, false, stream_context_create($context)) or die('Could not request ARTE.');
$input = getContents($url, $header) or die('Could not request ARTE.');
$input_json = json_decode($input, true);
foreach($input_json['videos'] as $element) {

View File

@@ -15,8 +15,13 @@ class DansTonChatBridge extends BridgeAbstract {
foreach($html->find('div.item') as $element) {
$item = array();
$item['uri'] = $element->find('a', 0)->href;
$item['title'] = 'DansTonChat ' . $element->find('a', 1)->plaintext;
$item['content'] = $element->find('a', 0)->innertext;
$titleContent = $element->find('h3 a', 0);
if($titleContent) {
$item['title'] = 'DansTonChat ' . html_entity_decode($titleContent->plaintext, ENT_QUOTES);
} else {
$item['title'] = 'DansTonChat';
}
$item['content'] = $element->find('div.item-content a', 0)->innertext;
$this->items[] = $item;
}
}

View File

@@ -160,9 +160,6 @@ class DealabsBridge extends BridgeAbstract {
'cept-tt',
'thread-link',
'linkPlain',
'space--r-1',
'size--all-s',
'size--fromW3-m',
)
);
@@ -208,7 +205,7 @@ class DealabsBridge extends BridgeAbstract {
foreach($list as $deal) {
$item = array();
$item['uri'] = $deal->find('div[class=threadGrid-title]', 0)->find('a', 0)->href;
$item['title'] = $deal->find('a[class='. $selectorLink .']', 0
$item['title'] = $deal->find('a[class*='. $selectorLink .']', 0
)->plaintext;
$item['author'] = $deal->find('span.thread-username', 0)->plaintext;
$item['content'] = '<table><tr><td><a href="'
@@ -217,9 +214,9 @@ class DealabsBridge extends BridgeAbstract {
. '"><img src="'
. $this->getImage($deal)
. '"/></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>'
. $this->getPrix($deal)
. $this->getReduction($deal)

View File

@@ -4,143 +4,163 @@ class DemonoidBridge extends BridgeAbstract {
const MAINTAINER = 'metaMMA';
const NAME = 'Demonoid';
const URI = 'https://www.demonoid.pw/';
const DESCRIPTION = 'Returns results for the keywords (in all categories or
a specific category). You can put several keywords separated by a semicolon
(e.g. "one show;another show"). Searches can by done in a specific category;
category number must be specified. (All=0, Movies=1, Music=2, TV=3, Games=4,
Applications=5, Pictures=8, Anime=9, Comics=10, Books=11 Music Videos=8,
Audio Books=17). User feed takes the Uploader ID number (not uploader name)
as keyword. Uploader ID is found by clicking on uploader, clicking on
"View this user\'s torrents", and copying the number after "uid=". An entire
category feed is accomplished by leaving "keywords" box blank and using the
corresponding category number.';
const DESCRIPTION = 'Returns results from search';
const PARAMETERS = array( array(
const PARAMETERS = array(array(
'q' => array(
'name' => 'keywords/user ID/category, separated by semicolons',
'exampleValue' => 'first list;second list;…',
'required' => true
),
'crit' => array(
'name' => 'keywords',
'exampleValue' => 'keyword1 keyword2…',
'required' => true,
),
'category' => array(
'name' => 'Category',
'type' => 'list',
'name' => 'Feed type',
'values' => array(
'search' => 'search',
'category' => 'cat',
'user' => 'usr'
'All' => 0,
'Movies' => 1,
'Music' => 2,
'TV' => 3,
'Games' => 4,
'Applications' => 5,
'Pictures' => 8,
'Anime' => 9,
'Comics' => 10,
'Books' => 11,
'Audiobooks' => 17
)
)
),
'catCheck' => array(
'type' => 'checkbox',
'name' => 'Specify category for keyword search ?',
),
'cat' => array(
'name' => 'Category number',
),
));
), array(
'catOnly' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 0,
'Movies' => 1,
'Music' => 2,
'TV' => 3,
'Games' => 4,
'Applications' => 5,
'Pictures' => 8,
'Anime' => 9,
'Comics' => 10,
'Books' => 11,
'Audiobooks' => 17
)
)
), array(
'userid' => array(
'name' => 'user id',
'exampleValue' => '00000',
'required' => true,
'type' => 'number'
),
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 0,
'Movies' => 1,
'Music' => 2,
'TV' => 3,
'Games' => 4,
'Applications' => 5,
'Pictures' => 8,
'Anime' => 9,
'Comics' => 10,
'Books' => 11,
'Audiobooks' => 17
)
)
)
);
public function collectData() {
$catBool = $this->getInput('catCheck');
if($catBool) {
$catNum = $this->getInput('cat');
if(!empty($this->getInput('q'))) {
$html = getSimpleHTMLDOM(
self::URI .
'files/?category=' .
rawurlencode($this->getInput('category')) .
'&subcategory=All&quality=All&seeded=2&external=2&query=' .
urlencode($this->getInput('q')) .
'&uid=0&sort='
) or returnServerError('Could not request Demonoid.');
} elseif(!empty($this->getInput('catOnly'))) {
$html = getSimpleHTMLDOM(
self::URI .
'files/?uid=0&category=' .
rawurlencode($this->getInput('catOnly')) .
'&subcategory=0&language=0&seeded=2&quality=0&query=&sort='
) or returnServerError('Could not request Demonoid.');
} elseif(!empty($this->getInput('userid'))) {
$html = getSimpleHTMLDOM(
self::URI .
'files/?uid=' .
rawurlencode($this->getInput('userid')) .
'&seeded=2'
) or returnServerError('Could not request Demonoid.');
} else {
returnServerError('Invalid parameters !');
}
$critList = $this->getInput('crit');
$keywordsList = explode(';', $this->getInput('q'));
foreach($keywordsList as $keywords) {
switch($critList) {
case 'search':
if($catBool == false) {
$html = file_get_contents(
self::URI .
'files/?category=0&subcategory=All&quality=All&seeded=2&external=2&query=' .
urlencode($keywords) . #not rawurlencode so space -> '+'
'&uid=0&sort='
) or returnServerError('Could not request Demonoid.');
} else {
$html = file_get_contents(
self::URI .
'files/?category=' .
rawurlencode($catNum) .
'&subcategory=All&quality=All&seeded=2&external=2&query=' .
urlencode($keywords) . #not rawurlencode so space -> '+'
'&uid=0&sort='
) or returnServerError('Could not request Demonoid.');
}
break;
case 'usr':
$html = file_get_contents(
self::URI .
'files/?uid=' .
rawurlencode($keywords) .
'&seeded=2'
) or returnServerError('Could not request Demonoid.');
break;
case 'cat':
$html = file_get_contents(
self::URI .
'files/?uid=0&category=' .
rawurlencode($keywords) .
'&subcategory=0&language=0&seeded=2&quality=0&query=&sort='
) or returnServerError('Could not request Demonoid.');
break;
if(preg_match('~No torrents found~', $html)) {
return;
}
$table = $html->find('td[class=ctable_content_no_pad]', 0);
$cursorCount = 4;
$elementCount = 0;
while($elementCount != 40) {
$elementCount++;
$currentElement = $table->find('tr', $cursorCount);
if(preg_match('~items total~', $currentElement)) {
break;
}
if(preg_match('~No torrents found~', $html)) {
returnServerError('No result for query ' . $keywords);
}
$bigTable = explode('<!-- start torrent list -->', $html)[1];
$last50 = explode('<!-- end torrent list -->', $bigTable)[0];
$dateChunk = explode('added_today', $last50);
$item = array ();
for($block = 1;$block < count($dateChunk);$block++) {
preg_match('~(?<=>Add).*?(?=<)~', $dateChunk[$block], $dateStr);
$item = array();
//Do we have a date ?
if(preg_match('~Added.*?(.*)~', $currentElement->plaintext, $dateStr)) {
if(preg_match('~today~', $dateStr[0])) {
date_default_timezone_set('UTC');
$timestamp = mktime(0, 0, 0, gmdate('n'), gmdate('j'), gmdate('Y'));
} else {
preg_match('~(?<=ed on ).*\d+~', $dateStr[0], $fullDateStr);
} else {
preg_match('~(?<=ed on ).*\d+~', $currentElement->plaintext, $fullDateStr);
date_default_timezone_set('UTC');
$dateObj = strptime($fullDateStr[0], '%A, %b %d, %Y');
$timestamp = mktime(0, 0, 0, $dateObj['tm_mon'] + 1, $dateObj['tm_mday'], 1900 + $dateObj['tm_year']);
}
$itemsChunk = explode('<!-- tstart -->', $dateChunk[$block]);
for($items = 1;$items < count($itemsChunk);$items++) {
$item = array();
$cols = explode('<td', $itemsChunk[$items]);
preg_match('~(?<=href=\"/).*?(?=\")~', $cols[1], $matches);
$item['id'] = self::URI . $matches[0];
preg_match('~(?<=href=\").*?(?=\")~', $cols[4], $matches);
$item['uri'] = $matches[0];
$item['timestamp'] = $timestamp;
preg_match('~(?<=href=\"/users/).*?(?=\")~', $cols[3], $matches);
$item['author'] = $matches[0];
preg_match('~(?<=/\">).*?(?=</a>)~', $cols[1], $matches);
$item['title'] = $matches[0];
preg_match('~(?<=green\">)\d+(?=</font>)~', $cols[8], $matches);
$item['seeders'] = $matches[0];
preg_match('~(?<=red\">)\d+(?=</font>)~', $cols[9], $matches);
$item['leechers'] = $matches[0];
preg_match('~(?<=>).*?(?=</td>)~', $cols[5], $matches);
$item['size'] = $matches[0];
$item['content'] = 'Uploaded by ' . $item['author']
. ' , Size ' . $item['size']
. '<br>seeders: '
. $item['seeders']
. ' | leechers: '
. $item['leechers']
. '<br><a href="'
. $item['id']
. '">info page</a>';
if(isset($item['title']))
$this->items[] = $item;
}
$cursorCount++;
}
$content = $table->find('tr', $cursorCount)->find('a', 1);
$cursorCount++;
$torrentInfo = $table->find('tr', $cursorCount);
$item['timestamp'] = $timestamp;
$item['title'] = $content->plaintext;
$item['id'] = self::URI . $content->href;
$item['uri'] = self::URI . $content->href;
$item['author'] = $torrentInfo->find('a[class=user]', 0)->plaintext;
$item['seeders'] = $torrentInfo->find('font[class=green]', 0)->plaintext;
$item['leechers'] = $torrentInfo->find('font[class=red]', 0)->plaintext;
$item['size'] = $torrentInfo->find('td', 3)->plaintext;
$item['content'] = 'Uploaded by ' . $item['author']
. ' , Size ' . $item['size']
. '<br>seeders: '
. $item['seeders']
. ' | leechers: '
. $item['leechers']
. '<br><a href="'
. $item['id']
. '">info page</a>';
$this->items[] = $item;
$cursorCount++;
}
}
}

112
bridges/DiscogsBridge.php Normal file
View 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;
}
}

54
bridges/FDroidBridge.php Normal file
View 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;
}
}
}

View File

@@ -96,17 +96,15 @@ class FacebookBridge extends BridgeAbstract {
$captcha_action = $_SESSION['captcha_action'];
$captcha_fields = $_SESSION['captcha_fields'];
$captcha_fields['captcha_response'] = preg_replace("/[^a-zA-Z0-9]+/", "", $_POST['captcha_response']);
$http_options = array(
'http' => array(
'method' => 'POST',
'user_agent' => ini_get('user_agent'),
'header' => array("Content-type:
application/x-www-form-urlencoded\r\nReferer: $captcha_action\r\nCookie: noscript=1\r\n"),
'content' => http_build_query($captcha_fields)
),
$header = array("Content-type:
application/x-www-form-urlencoded\r\nReferer: $captcha_action\r\nCookie: noscript=1\r\n");
$opts = array(
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => http_build_query($captcha_fields)
);
$context = stream_context_create($http_options);
$html = getContents($captcha_action, false, $context);
$html = getContents($captcha_action, $header, $opts);
if($html === false) {
returnServerError('Failed to submit captcha response back to Facebook');
@@ -120,23 +118,18 @@ class FacebookBridge extends BridgeAbstract {
//Retrieve page contents
if(is_null($html)) {
$http_options = array(
'http' => array(
'method' => 'GET',
'user_agent' => ini_get('user_agent'),
'header' => 'Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n"
)
);
$context = stream_context_create($http_options);
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n");
// First character cannot be a forward slash
if(strpos($this->getInput('u'), "/") === 0) {
returnClientError('Remove leading slash "/" from the username!');
}
if(!strpos($this->getInput('u'), "/")) {
$html = getSimpleHTMLDOM(self::URI . urlencode($this->getInput('u')) . '?_fb_noscript=1',
false,
$context)
$html = getSimpleHTMLDOM(self::URI . urlencode($this->getInput('u')) . '?_fb_noscript=1', $header)
or returnServerError('No results for this query.');
} else {
$html = getSimpleHTMLDOM(self::URI . 'pages/' . $this->getInput('u') . '?_fb_noscript=1',
false,
$context)
$html = getSimpleHTMLDOM(self::URI . 'pages/' . $this->getInput('u') . '?_fb_noscript=1', $header)
or returnServerError('No results for this query.');
}
}

View File

@@ -3,7 +3,7 @@ class GoComicsBridge extends BridgeAbstract {
const MAINTAINER = 'sky';
const NAME = 'GoComics Unofficial RSS';
const URI = 'http://www.gocomics.com/';
const URI = 'https://www.gocomics.com/';
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'The Unofficial GoComics RSS';
const PARAMETERS = array( array(
@@ -18,25 +18,27 @@ class GoComicsBridge extends BridgeAbstract {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request GoComics: ' . $this->getURI());
foreach($html->find('div.comic__container') as $element) {
//Get info from first page
$author = preg_replace('/By /', '', $html->find(".media-subheading", 0)->plaintext);
$img = $element->find('.item-comic-image img', 0);
$link = $element->find('a.js-item-comic-link', 0);
$comic = $img->src;
$title = $link->title;
$url = $html->find('input.js-copy-link', 0)->value;
$date = substr($title, -10);
if (empty($title))
$title = 'GoComics ' . $this->getInput('comicname') . ' on ' . $date;
$date = strtotime($date);
$link = self::URI . $html->find(".gc-deck--cta-0", 0)->find('a', 0)->href;
for($i = 0; $i < 5; $i++) {
$item = array();
$item['id'] = $url;
$item['uri'] = $url;
$item['title'] = $title;
$item['author'] = preg_replace('/by /', '', $element->find('a.link-blended small', 0)->plaintext);
$item['timestamp'] = $date;
$item['content'] = '<img src="' . $comic . '" alt="' . $title . '" />';
$page = getSimpleHTMLDOM($link)
or returnServerError('Could not request GoComics: ' . $link);
$imagelink = $page->find(".img-fluid", 1)->src;
$date = explode("/", $link);
$item['id'] = $imagelink;
$item['uri'] = $link;
$item['author'] = $author;
$item['title'] = 'GoComics ' . $this->getInput('comicname');
$item['timestamp'] = DateTime::createFromFormat("Ymd", $date[5] . $date[6] . $date[7])->getTimestamp();
$item['content'] = '<img src="' . $imagelink . '" />';
$link = self::URI . $page->find(".js-previous-comic", 0)->href;
$this->items[] = $item;
}
}

View File

@@ -18,8 +18,8 @@ class IPBBridge extends FeedExpander {
'name' => 'Limit',
'type' => 'number',
'required' => false,
'title' => 'Specify how many pages should be fetched (-1: all)',
'defaultValue' => 1
'title' => 'Specifies the number of items to return on each request (-1: all)',
'defaultValue' => 10
)
)
);
@@ -161,15 +161,18 @@ class IPBBridge extends FeedExpander {
$next = null; // Holds the URI of the next page
do {
// Skip loading HTML on first iteration
if(!is_null($next)) {
$html = getSimpleHTMLDOMCached($next);
while(true) {
$next = $this->$callback($html, is_null($next));
if(is_null($next) || ($limit > 0 && count($this->items) >= $limit)) {
break;
}
$next = $this->$callback($html, is_null($next));
$limit--;
} while(!is_null($next) && $limit <> 0);
$html = getSimpleHTMLDOMCached($next);
}
// We might have more items than specified, remove excess
$this->items = array_slice($this->items, 0, $limit);
}
private function collectTopicArticle($html, $firstrun = true){

View File

@@ -6,23 +6,42 @@ class InstagramBridge extends BridgeAbstract {
const URI = 'https://instagram.com/';
const DESCRIPTION = 'Returns the newest images';
const PARAMETERS = array( array(
'u' => array(
'name' => 'username',
'required' => true
),
'media_type' => array(
'name' => 'Media type',
'type' => 'list',
'required' => false,
'values' => array(
'Both' => 'all',
'Video' => 'video',
'Picture' => 'picture'
const PARAMETERS = array(
array(
'u' => array(
'name' => 'username',
'required' => true
),
'defaultValue' => 'all'
'media_type' => array(
'name' => 'Media type',
'type' => 'list',
'required' => false,
'values' => array(
'Both' => 'all',
'Video' => 'video',
'Picture' => 'picture'
),
'defaultValue' => 'all'
)
),
array(
'h' => array(
'name' => 'hashtag',
'required' => true
),
'media_type' => array(
'name' => 'Media type',
'type' => 'list',
'required' => false,
'values' => array(
'Both' => 'all',
'Video' => 'video',
'Picture' => 'picture'
),
'defaultValue' => 'all'
)
)
));
);
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
@@ -47,9 +66,14 @@ class InstagramBridge extends BridgeAbstract {
$json = trim(substr($innertext, $pos + 18), ' =;');
$data = json_decode($json);
$userMedia = $data->entry_data->ProfilePage[0]->user->media->nodes;
if(!is_null($this->getInput('u'))) {
$userMedia = $data->entry_data->ProfilePage[0]->graphql->user->edge_owner_to_timeline_media->edges;
} else {
$userMedia = $data->entry_data->TagPage[0]->graphql->hashtag->edge_hashtag_to_media->edges;
}
foreach($userMedia as $media) {
$media = $media->node;
// Check media type
switch($this->getInput('media_type')) {
case 'all': break;
@@ -63,14 +87,15 @@ class InstagramBridge extends BridgeAbstract {
}
$item = array();
$item['uri'] = self::URI . 'p/' . $media->code . '/';
$item['content'] = '<img src="' . htmlentities($media->display_src) . '" />';
if (isset($media->caption)) {
$item['title'] = $media->caption;
$item['uri'] = self::URI . 'p/' . $media->shortcode . '/';
$item['content'] = '<img src="' . htmlentities($media->display_url) . '" />';
if (isset($media->edge_media_to_caption->edges[0]->node->text)) {
$item['title'] = $media->edge_media_to_caption->edges[0]->node->text;
} else {
$item['title'] = basename($media->display_src);
$item['title'] = basename($media->display_url);
}
$item['timestamp'] = $media->date;
$item['timestamp'] = $media->taken_at_timestamp;
$item['enclosures'] = array($media->display_url);
$this->items[] = $item;
}
}
@@ -86,6 +111,8 @@ class InstagramBridge extends BridgeAbstract {
public function getURI(){
if(!is_null($this->getInput('u'))) {
return self::URI . urlencode($this->getInput('u'));
} elseif(!is_null($this->getInput('h'))) {
return self::URI . 'explore/tags/' . urlencode($this->getInput('h'));
}
return parent::getURI();

View File

@@ -45,9 +45,7 @@ class KernelBugTrackerBridge extends BridgeAbstract {
// We use the print preview page for simplicity
$html = getSimpleHTMLDOMCached($this->getURI() . '&format=multiple',
86400,
false,
null,
0,
null,
true,
true,

View 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();
}
}

73
bridges/PixivBridge.php Normal file
View 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";
}
}

View File

@@ -0,0 +1,30 @@
<?php
class RadioMelodieBridge extends BridgeAbstract {
const NAME = 'Radio Melodie Actu';
const URI = 'https://www.radiomelodie.com/';
const DESCRIPTION = 'Retourne les actualités publiées par Radio Melodie';
const MAINTAINER = 'sysadminstory';
public function collectData(){
$html = getSimpleHTMLDOM(self::URI . 'actu')
or returnServerError('Could not request Radio Melodie.');
$list = $html->find('div[class=actuitem]');
foreach($list as $element) {
$item = array();
// Get picture URL
$pictureHTML = $element->find('div[class=picture]');
preg_match(
'/background-image:url\((.*)\);/',
$pictureHTML[0]->getAttribute('style'),
$pictures);
$pictureURL = $pictures[1];
$item['enclosures'] = array($pictureURL);
$item['uri'] = self::URI . $element->parent()->href;
$item['title'] = $element->find('h3', 0)->plaintext;
$item['content'] = $element->find('p', 0)->plaintext . '<br/><img src="'.$pictureURL.'"/>';
$this->items[] = $item;
}
}
}

View File

@@ -14,7 +14,7 @@ class SoundCloudBridge extends BridgeAbstract {
)
));
const CLIENT_ID = '0aca19eae3843844e4053c6d8fdb7875';
const CLIENT_ID = '4jkoEFmZEDaqjwJ9Eih4ATNhcH3vMVfp';
public function collectData(){

View File

@@ -4,7 +4,7 @@ class SteamBridge extends BridgeAbstract {
const NAME = 'Steam Bridge';
const URI = 'https://store.steampowered.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns games list';
const DESCRIPTION = 'Returns apps list';
const MAINTAINER = 'jacknumber';
const PARAMETERS = array(
'Wishlist' => array(
@@ -47,16 +47,6 @@ class SteamBridge extends BridgeAbstract {
'AED' => 'ae',
),
),
'sort' => array(
'name' => 'Sort by',
'type' => 'list',
'values' => array(
'Rank' => 'rank',
'Date Added' => 'added',
'Name' => 'name',
'Price' => 'price',
)
),
'only_discount' => array(
'name' => 'Only discount',
'type' => 'checkbox',
@@ -68,49 +58,44 @@ class SteamBridge extends BridgeAbstract {
$username = $this->getInput('username');
$params = array(
'cc' => $this->getInput('currency'),
'sort' => $this->getInput('sort')
'cc' => $this->getInput('currency')
);
$url = self::URI . 'wishlist/id/' . $username . '/?' . http_build_query($params);
$url = self::URI . 'wishlist/id/' . $username . '?' . http_build_query($params);
$jsonDataRegex = '/var g_rg(?:WishlistData|AppInfo) = ([^;]*)/';
$content = getContents($url)
$targetVariable = 'g_rgAppInfo';
$sort = array();
$html = '';
$html = getSimpleHTMLDOM($url)
or returnServerError("Could not request Steam Wishlist. Tried:\n - $url");
preg_match_all($jsonDataRegex, $content, $matches, PREG_SET_ORDER, 0);
$appList = json_decode($matches[0][1], true);
$fullAppList = json_decode($matches[1][1], true);
//var_dump($matches[1][1]);
//var_dump($fullAppList);
$sortedElementList = array_fill(0, count($appList), 0);
foreach($appList as $app) {
$sortedElementList[$app["priority"] - 1] = $app["appid"];
$jsContent = $html->find('.responsive_page_template_content script', 0)->innertext;
if(preg_match('/var ' . $targetVariable . ' = (.*?);/s', $jsContent, $matches)) {
$appsData = json_decode($matches[1]);
} else {
returnServerError("Could not parse JS variable ($targetVariable) in page content.");
}
foreach($sortedElementList as $appId) {
foreach($appsData as $id => $element) {
$app = $fullAppList[$appId];
$gameTitle = $app["name"];
$gameUri = "http://store.steampowered.com/app/" . $appId . "/";
$gameImg = $app["capsule"];
$appType = $element->type;
$appIsBuyable = 0;
$appHasDiscount = 0;
$appIsFree = 0;
$item = array();
$item['uri'] = $gameUri;
$item['title'] = $gameTitle;
if($element->subs) {
$appIsBuyable = 1;
if(count($app["subs"]) > 0) {
if($app["subs"][0]["discount_pct"] != 0) {
if($element->subs[0]->discount_pct) {
$item['promoValue'] = $app["subs"][0]["discount_pct"];
$item['oldPrice'] = $app["subs"][0]["price"] / 100 / ((100 - $gamePromoValue / 100));
$item['newPrice'] = $app["subs"][0]["price"] / 100;
$item['price'] = $item['newPrice'];
$item['hasPromo'] = true;
$appHasDiscount = 1;
$discountBlock = str_get_html($element->subs[0]->discount_block);
$appDiscountValue = $discountBlock->find('.discount_pct', 0)->plaintext;
$appOldPrice = $discountBlock->find('.discount_original_price', 0)->plaintext;
$appNewPrice = $discountBlock->find('.discount_final_price', 0)->plaintext;
$appPrice = $appNewPrice;
} else {
@@ -118,15 +103,55 @@ class SteamBridge extends BridgeAbstract {
continue;
}
$item['price'] = $app["subs"][0]["price"] / 100;
$item['hasPromo'] = false;
$appPrice = $element->subs[0]->price / 100;
}
} else {
if($this->getInput('only_discount')) {
continue;
}
if(isset($element->free) && $element->free = 1) {
$appIsFree = 1;
}
}
$item = array();
$item['uri'] = "http://store.steampowered.com/app/$id/";
$item['title'] = $element->name;
$item['type'] = $appType;
$item['cover'] = str_replace('_292x136', '', $element->capsule);
$item['timestamp'] = $element->added;
$item['isBuyable'] = $appIsBuyable;
$item['hasDiscount'] = $appHasDiscount;
$item['isFree'] = $appIsFree;
$item['priority'] = $element->priority;
if($appIsBuyable) {
$item['price'] = floatval(str_replace(',', '.', $appPrice));
}
if($appHasDiscount) {
$item['discount']['value'] = $appDiscountValue;
$item['discount']['oldPrice'] = floatval(str_replace(',', '.', $appOldPrice));
$item['discount']['newPrice'] = floatval(str_replace(',', '.', $appNewPrice));
}
$this->items[] = $item;
$item['enclosures'] = array();
$item['enclosures'][] = str_replace('_292x136', '', $element->capsule);
foreach($element->screenshots as $screenshot) {
$item['enclosures'][] = substr($element->capsule, 0, -31) . $screenshot;
}
$sort[$id] = $element->priority;
$this->items[] = $item;
}
array_multisort($sort, SORT_ASC, $this->items);
}
}

57
bridges/SupInfoBridge.php Normal file
View 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;
}
}

View File

@@ -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++;
}
}
}

View File

@@ -43,40 +43,225 @@ class VkBridge extends BridgeAbstract
or returnServerError('No results for group or user name "' . $this->getInput('u') . '".');
$text_html = iconv('windows-1251', 'utf-8', $text_html);
// makes album link generating work correctly
$text_html = str_replace('"class="page_album_link">', '" class="page_album_link">', $text_html);
$html = str_get_html($text_html);
$pageName = $html->find('.page_name', 0)->plaintext;
$this->pageName = $pageName;
$pageName = $html->find('.page_name', 0);
if (is_object($pageName)) {
$pageName = $pageName->plaintext;
$this->pageName = htmlspecialchars_decode($pageName);
}
$pinned_post_item = null;
$last_post_id = 0;
foreach ($html->find('.post') as $post) {
$is_pinned_post = false;
if (strpos($post->getAttribute('class'), 'post_fixed') !== false) {
$is_pinned_post = true;
}
if (is_object($post->find('a.wall_post_more', 0))) {
//delete link "show full" in content
$post->find('a.wall_post_more', 0)->outertext = '';
}
$content_suffix = "";
// looking for external links
$external_link_selectors = array(
'a.page_media_link_title',
'div.page_media_link_title > a',
'div.media_desc > a.lnk',
);
foreach($external_link_selectors as $sel) {
if (is_object($post->find($sel, 0))) {
$a = $post->find($sel, 0);
$innertext = $a->innertext;
$parsed_url = parse_url($a->getAttribute('href'));
if (strpos($parsed_url['path'], '/away.php') !== 0) continue;
parse_str($parsed_url["query"], $parsed_query);
$content_suffix .= "<br>External link: <a href='" . $parsed_query["to"] . "'>$innertext</a>";
}
}
// remove external link from content
$external_link_selectors_to_remove = array(
'div.page_media_thumbed_link',
'div.page_media_link_desc_wrap',
'div.media_desc > a.lnk',
);
foreach($external_link_selectors_to_remove as $sel) {
if (is_object($post->find($sel, 0))) {
$post->find($sel, 0)->outertext = '';
}
}
// looking for article
$article = $post->find("a.article_snippet", 0);
if (is_object($article)) {
$article_title = $article->find("div.article_snippet__title", 0)->innertext;
$article_author = $article->find("div.article_snippet__author", 0)->innertext;
$article_link = self::URI . ltrim($article->getAttribute('href'), '/');
$article_img_element_style = $article->find("div.article_snippet__image", 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 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 sign
$post_author = $pageName;
foreach($post->find('a.wall_signed_by') as $a) {
$post_author = $a->innertext;
$a->outertext = '';
}
if (is_object($post->find('div.copy_quote', 0))) {
$copy_quote = $post->find('div.copy_quote', 0);
if ($copy_post_header = $copy_quote->find('div.copy_post_header', 0)) {
$copy_post_header->outertext = '';
}
$copy_quote_content = $copy_quote->innertext;
$copy_quote->outertext = "<br>Reposted: <br>$copy_quote_content";
}
$item = array();
$item['content'] = strip_tags(backgroundToImg($post->find('div.wall_text', 0)->innertext), '<br><img>');
if (is_object($post->find('a.page_media_link_title', 0))) {
$link = $post->find('a.page_media_link_title', 0)->getAttribute('href');
//external link in the post
$item['content'] .= "\n\rExternal link: "
. str_replace('/away.php?to=', '', urldecode($link));
}
//get video on post
if (is_object($post->find('span.post_video_title_content', 0))) {
$titleVideo = $post->find('span.post_video_title_content', 0)->plaintext;
$linkToVideo = self::URI . $post->find('a.page_post_thumb_video', 0)->getAttribute('href');
$item['content'] .= "\n\r {$titleVideo}: {$linkToVideo}";
}
$item['content'] .= $content_suffix;
// get post link
$item['uri'] = self::URI . $post->find('a.post_link', 0)->getAttribute('href');
$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['author'] = $pageName;
$this->items[] = $item;
$item['title'] = $this->getTitle($item['content']);
$item['author'] = $post_author;
if ($is_pinned_post) {
// do not append it now
$pinned_post_item = $item;
} else {
$last_post_id = $item['post_id'];
$this->items[] = $item;
}
}
if (is_null($pinned_post_item)) {
return;
} else if (count($this->items) == 0) {
$this->items[] = $pinned_post_item;
} else if ($last_post_id < $pinned_post_item['post_id']) {
$this->items[] = $pinned_post_item;
usort($this->items, function ($item1, $item2) {
return $item2['post_id'] - $item1['post_id'];
});
}
}
private function getPhoto($a) {
$onclick = $a->getAttribute('onclick');
preg_match('/return showPhoto\(.+?({.*})/', $onclick, $preg_match_result);
if (count($preg_match_result) == 0) return;
$arg = htmlspecialchars_decode( str_replace('queue:1', '"queue":1', $preg_match_result[1]) );
$data = json_decode($arg, true);
if ($data == null) return;
$thumb = $data['temp']['base'] . $data['temp']['x_'][0] . ".jpg";
$original = '';
foreach(array('y_', 'z_', 'w_') as $key) {
if (!isset($data['temp'][$key])) continue;
$original = $data['temp']['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)
@@ -109,19 +294,9 @@ class VkBridge extends BridgeAbstract
{
ini_set('user-agent', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0');
$opts = array(
'http' => array(
'method' => "GET",
'user_agent' => ini_get('user_agent'),
'accept_encoding' => 'gzip',
'header' => "Accept-language: en\r\n
Cookie: remixlang=3\r\n"
)
);
$header = array('Accept-language: en', 'Cookie: remixlang=3');
$context = stream_context_create($opts);
return getContents($this->getURI(), false, $context);
return getContents($this->getURI(), $header);
}

View File

@@ -0,0 +1,138 @@
<?php
/* This is a mashup of FlickrExploreBridge by sebsauvage and FlickrTagBridge
* by erwang.providing the functionality of both in one.
*/
class YGGTorrentBridge extends BridgeAbstract {
const MAINTAINER = 'teromene';
const NAME = 'Yggtorrent Bridge';
const URI = 'https://yggtorrent.is';
const DESCRIPTION = 'Returns torrent search from Yggtorrent';
const PARAMETERS = array(
array(
"cat" => array(
"name" => "category",
"type" => "list",
"values" => array(
"Toute les catégories" => "all.all",
"Film/Vidéo - Toutes les sous-catégories" => "2145.all",
"Film/Vidéo - Animation" => "2145.2178",
"Film/Vidéo - Animation Série" => "2145.2179",
"Film/Vidéo - Concert" => "2145.2180",
"Film/Vidéo - Documentaire" => "2145.2181",
"Film/Vidéo - Émission TV" => "2145.2182",
"Film/Vidéo - Film" => "2145.2183",
"Film/Vidéo - Série TV" => "2145.2184",
"Film/Vidéo - Spectacle" => "2145.2185",
"Film/Vidéo - Sport" => "2145.2186",
"Film/Vidéo - Vidéo-clips" => "2145.2186",
"Audio - Toutes les sous-catégories" => "2139.all",
"Audio - Karaoké" => "2139.2147",
"Audio - Musique" => "2139.2148",
"Audio - Podcast Radio" => "2139.2150",
"Audio - Samples" => "2139.2149",
"Jeu vidéo - Toutes les sous-catégories" => "2142.all",
"Jeu vidéo - Autre" => "2142.2167",
"Jeu vidéo - Linux" => "2142.2159",
"Jeu vidéo - MacOS" => "2142.2160",
"Jeu vidéo - Microsoft" => "2142.2162",
"Jeu vidéo - Nintendo" => "2142.2163",
"Jeu vidéo - Smartphone" => "2142.2165",
"Jeu vidéo - Sony" => "2142.2164",
"Jeu vidéo - Tablette" => "2142.2166",
"Jeu vidéo - Windows" => "2142.2161",
"eBook - Toutes les sous-catégories" => "2140.all",
"eBook - Audio" => "2140.2151",
"eBook - Bds" => "2140.2152",
"eBook - Comics" => "2140.2153",
"eBook - Livres" => "2140.2154",
"eBook - Mangas" => "2140.2155",
"eBook - Presse" => "2140.2156",
"Emulation - Toutes les sous-catégories" => "2141.all",
"Emulation - Emulateurs" => "2141.2157",
"Emulation - Roms" => "2141.2158",
"GPS - Toutes les sous-catégories" => "2141.all",
"GPS - Applications" => "2141.2168",
"GPS - Cartes" => "2141.2169",
"GPS - Divers" => "2141.2170"
)
),
"nom" => array(
"name" => "Nom",
"description" => "Nom du torrent",
"type" => "text"
),
"description" => array(
"name" => "Description",
"description" => "Description du torrent",
"type" => "text"
),
"fichier" => array(
"name" => "Fichier",
"description" => "Fichier du torrent",
"type" => "text"
),
"uploader" => array(
"name" => "Uploader",
"description" => "Uploader du torrent",
"type" => "text"
),
)
);
public function collectData() {
$catInfo = explode(".", $this->getInput("cat"));
$category = $catInfo[0];
$subcategory = $catInfo[1];
$html = getSimpleHTMLDOM(self::URI . "/engine/search?name="
. $this->getInput("nom")
. "&description="
. $this->getInput("description")
. "&fichier="
. $this->getInput("fichier")
. "&file="
. $this->getInput("uploader")
. "&category="
. $category
. "&sub_category="
. $subcategory
. "&do=search")
or returnServerError("Unable to query Yggtorrent !");
$count = 0;
foreach($html->find(".results", 0)->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[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);
}
}

View File

@@ -110,8 +110,8 @@ class YoutubeBridge extends BridgeAbstract {
$this->feedName = $this->ytBridgeFixTitle($xml->find('feed > title', 0)->plaintext); // feedName will be used by getName()
}
private function ytBridgeParseHtmlListing($html, $element_selector, $title_selector){
$limit = 10;
private function ytBridgeParseHtmlListing($html, $element_selector, $title_selector, $add_parsed_items = true) {
$limit = $add_parsed_items ? 10 : INF;
$count = 0;
foreach($html->find($element_selector) as $element) {
if($count < $limit) {
@@ -122,12 +122,15 @@ class YoutubeBridge extends BridgeAbstract {
$vid = substr($vid, 0, strpos($vid, '&') ?: strlen($vid));
$title = $this->ytBridgeFixTitle($element->find($title_selector, 0)->plaintext);
if($title != '[Private Video]' && strpos($vid, 'googleads') === false) {
$this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time);
$this->ytBridgeAddItem($vid, $title, $author, $desc, $time);
if ($add_parsed_items) {
$this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time);
$this->ytBridgeAddItem($vid, $title, $author, $desc, $time);
}
$count++;
}
}
}
return $count;
}
private function ytBridgeFixTitle($title) {
@@ -137,10 +140,8 @@ class YoutubeBridge extends BridgeAbstract {
private function ytGetSimpleHTMLDOM($url){
return getSimpleHTMLDOM($url,
$use_include_path = false,
$context = null,
$offset = 0,
$maxLen = null,
$header = array(),
$opts = array(),
$lowercase = true,
$forceTagsClosed = true,
$target_charset = DEFAULT_TARGET_CHARSET,
@@ -176,11 +177,20 @@ class YoutubeBridge extends BridgeAbstract {
}
} elseif($this->getInput('p')) { /* playlist mode */
$this->request = $this->getInput('p');
$url_feed = self::URI . 'feeds/videos.xml?playlist_id=' . urlencode($this->request);
$url_listing = self::URI . 'playlist?list=' . urlencode($this->request);
$html = $this->ytGetSimpleHTMLDOM($url_listing)
or returnServerError("Could not request YouTube. Tried:\n - $url_listing");
$this->ytBridgeParseHtmlListing($html, 'tr.pl-video', '.pl-video-title a');
$item_count = $this->ytBridgeParseHtmlListing($html, 'tr.pl-video', '.pl-video-title a', false);
if ($item_count <= 15 && ($xml = $this->ytGetSimpleHTMLDOM($url_feed))) {
$this->ytBridgeParseXmlFeed($xml);
} else {
$this->ytBridgeParseHtmlListing($html, 'tr.pl-video', '.pl-video-title a');
}
$this->feedName = 'Playlist: ' . str_replace(' - YouTube', '', $html->find('title', 0)->plaintext); // feedName will be used by getName()
usort($this->items, function ($item1, $item2) {
return $item2['timestamp'] - $item1['timestamp'];
});
} elseif($this->getInput('s')) { /* search mode */
$this->request = $this->getInput('s');
$page = 1;
@@ -197,7 +207,7 @@ class YoutubeBridge extends BridgeAbstract {
$html = $this->ytGetSimpleHTMLDOM($url_listing)
or returnServerError("Could not request YouTube. Tried:\n - $url_listing");
$this->ytBridgeParseHtmlListing($html, 'div.yt-lockup', 'h3');
$this->ytBridgeParseHtmlListing($html, 'div.yt-lockup', 'h3 > a');
$this->feedName = 'Search: ' . str_replace(' - YouTube', '', $html->find('title', 0)->plaintext); // feedName will be used by getName()
} else { /* no valid mode */
returnClientError("You must either specify either:\n - YouTube

View File

@@ -19,6 +19,10 @@ 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);
@@ -73,6 +77,12 @@ if(!extension_loaded('libxml'))
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');
@@ -168,6 +178,16 @@ try {
define('NOPROXY', true);
}
// Custom cache timeout
$cache_timeout = -1;
if(array_key_exists('_cache_timeout', $params)) {
if(!CUSTOM_CACHE_TIMEOUT) {
throw new \HttpException('This server doesn\'t support "_cache_timeout"!');
}
$cache_timeout = filter_var($params['_cache_timeout'], FILTER_VALIDATE_INT);
}
// Initialize cache
$cache = Cache::create('FileCache');
$cache->setPath(CACHE_DIR);
@@ -178,11 +198,17 @@ try {
unset($params['bridge']);
unset($params['format']);
unset($params['_noproxy']);
unset($params['_cache_timeout']);
// Load cache & data
try {
$bridge->setCache($cache);
$bridge->setCacheTimeout($cache_timeout);
$bridge->setDatas($params);
} catch(Error $e) {
http_response_code($e->getCode());
header('Content-Type: text/html');
die(buildBridgeException($e, $bridge));
} catch(Exception $e) {
http_response_code($e->getCode());
header('Content-Type: text/html');
@@ -195,10 +221,14 @@ try {
$format->setItems($bridge->getItems());
$format->setExtraInfos($bridge->getExtraInfos());
$format->display();
} catch(Exception $e) {
} catch(Error $e) {
http_response_code($e->getCode());
header('Content-Type: text/html');
die(buildTransformException($e, $bridge));
} catch(Exception $e) {
http_response_code($e->getCode());
header('Content-Type: text/html');
die(buildBridgeException($e, $bridge));
}
die;
@@ -273,7 +303,7 @@ EOD;
echo $inactiveBridges;
?>
<section class="footer">
<a href="https://github.com/RSS-Bridge/rss-bridge">RSS-Bridge 2017-08-19 ~ Public Domain</a><br />
<a href="https://github.com/RSS-Bridge/rss-bridge">RSS-Bridge 2018-04-20 ~ Public Domain</a><br />
<?= $activeFoundBridgeCount; ?>/<?= count($bridgeList) ?> active bridges. <br />
<?php
if($activeFoundBridgeCount !== count($bridgeList)) {

View File

@@ -14,6 +14,7 @@ abstract class BridgeAbstract implements BridgeInterface {
protected $items = array();
protected $inputs = array();
protected $queriedContext = '';
protected $cacheTimeout;
/**
* Return cachable datas (extrainfos and items) stored in the bridge
@@ -171,7 +172,7 @@ abstract class BridgeAbstract implements BridgeInterface {
if(!is_null($this->cache)) {
$time = $this->cache->getTime();
if($time !== false
&& (time() - static::CACHE_TIMEOUT < $time)
&& (time() - $this->getCacheTimeout() < $time)
&& (!defined('DEBUG') || DEBUG !== true)) {
$cached = $this->cache->loadData();
if(isset($cached['items']) && isset($cached['extraInfos'])) {
@@ -268,4 +269,17 @@ abstract class BridgeAbstract implements BridgeInterface {
public function setCache(\CacheInterface $cache){
$this->cache = $cache;
}
public function setCacheTimeout($timeout){
if(is_numeric($timeout) && ($timeout < 1 || $timeout > 86400)) {
$this->cacheTimeout = static::CACHE_TIMEOUT;
return;
}
$this->cacheTimeout = $timeout;
}
public function getCacheTimeout(){
return isset($this->cacheTimeout) ? $this->cacheTimeout : static::CACHE_TIMEOUT;
}
}

View File

@@ -68,4 +68,20 @@ interface BridgeInterface {
* @param object CacheInterface The cache instance
*/
public function setCache(\CacheInterface $cache);
/**
* Sets the timeout for clearing the cache files. The timeout must be
* specified between 1..86400 seconds (max. 24 hours). The default timeout
* (specified by the bridge maintainer) applies for invalid values.
*
* @param int $timeout The cache timeout in seconds
*/
public function setCacheTimeout($timeout);
/**
* Returns the cache timeout
*
* @return int Cache timeout
*/
public function getCacheTimeout();
}

View File

@@ -54,7 +54,7 @@ function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null
* provided parameter are invalid
*/
function buildBridgeException($e, $bridge){
if(!($e instanceof \Exception) || !($bridge instanceof \BridgeInterface)) {
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
return null;
}
@@ -85,7 +85,7 @@ unable to receive or process the remote website's content!";
* provided parameter are invalid
*/
function buildTransformException($e, $bridge){
if(!($e instanceof \Exception) || !($bridge instanceof \BridgeInterface)) {
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
return null;
}

View File

@@ -1,77 +1,45 @@
<?php
function getContents($url,
$use_include_path = false,
$context = null,
$offset = 0,
$maxlen = null){
$contextOptions = array(
'http' => array(
'user_agent' => ini_get('user_agent'),
'accept_encoding' => 'gzip'
)
);
function getContents($url, $header = array(), $opts = array()){
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
if(defined('PROXY_URL') && !defined('NOPROXY')) {
$contextOptions['http']['proxy'] = PROXY_URL;
$contextOptions['http']['request_fulluri'] = true;
if(is_array($header) && count($header) !== 0)
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
if(is_null($context)) {
$context = stream_context_create($contextOptions);
} else {
$prevContext = $context;
if(!stream_context_set_option($context, $contextOptions)) {
$context = $prevContext;
}
curl_setopt($ch, CURLOPT_USERAGENT, ini_get('user_agent'));
curl_setopt($ch, CURLOPT_ENCODING, '');
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
if(is_array($opts)) {
foreach($opts as $key => $value) {
curl_setopt($ch, $key, $value);
}
}
if(is_null($maxlen)) {
$content = file_get_contents($url, $use_include_path, $context, $offset);
} else {
$content = file_get_contents($url, $use_include_path, $context, $offset, $maxlen);
if(defined('PROXY_URL') && !defined('NOPROXY')) {
curl_setopt($ch, CURLOPT_PROXY, PROXY_URL);
}
$content = curl_exec($ch);
curl_close($ch);
if($content === false)
debugMessage('Cant\'t download ' . $url);
// handle compressed data
foreach($http_response_header as $header) {
if(stristr($header, 'content-encoding')) {
switch(true) {
case stristr($header, 'gzip'):
$content = gzinflate(substr($content, 10, -8));
break;
case stristr($header, 'compress'):
//TODO
case stristr($header, 'deflate'):
//TODO
case stristr($header, 'brotli'):
//TODO
returnServerError($header . '=> Not implemented yet');
break;
case stristr($header, 'identity'):
break;
default:
returnServerError($header . '=> Unknown compression');
}
}
}
return $content;
}
function getSimpleHTMLDOM($url,
$use_include_path = false,
$context = null,
$offset = 0,
$maxLen = null,
$header = array(),
$opts = array(),
$lowercase = true,
$forceTagsClosed = true,
$target_charset = DEFAULT_TARGET_CHARSET,
$stripRN = true,
$defaultBRText = DEFAULT_BR_TEXT,
$defaultSpanText = DEFAULT_SPAN_TEXT){
$content = getContents($url, $use_include_path, $context, $offset, $maxLen);
$content = getContents($url, $header, $opts);
return str_get_html($content,
$lowercase,
$forceTagsClosed,
@@ -89,10 +57,8 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
*/
function getSimpleHTMLDOMCached($url,
$duration = 86400,
$use_include_path = false,
$context = null,
$offset = 0,
$maxLen = null,
$header = array(),
$opts = array(),
$lowercase = true,
$forceTagsClosed = true,
$target_charset = DEFAULT_TARGET_CHARSET,
@@ -116,7 +82,7 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
&& (!defined('DEBUG') || DEBUG !== true)) { // Contents within duration
$content = $cache->loadData();
} else { // Content not within duration
$content = getContents($url, $use_include_path, $context, $offset, $maxLen);
$content = getContents($url, $header, $opts);
if($content !== false) {
$cache->saveData($content);
}

View File

@@ -75,8 +75,24 @@ CARD;
. ((defined('PROXY_NAME') && PROXY_NAME) ? PROXY_NAME : PROXY_URL)
. ')</label><br />'
. PHP_EOL;
}
} if(CUSTOM_CACHE_TIMEOUT) {
$idArg = 'arg-'
. urlencode($bridgeName)
. '-'
. urlencode('_cache_timeout');
$card .= '<label for="'
. $idArg
. '">Cache timeout in seconds : </label>'
. PHP_EOL;
$card .= '<input id="'
. $idArg
. '" type="number" value="'
. $bridge->getCacheTimeout()
. '" name="_cache_timeout" /><br />'
. PHP_EOL;
}
$card .= $getHelperButtonsFormat($formats);
} else {
$card .= '<span style="font-weight: bold;">Inactive</span>';
@@ -251,6 +267,23 @@ CARD;
. ((defined('PROXY_NAME') && PROXY_NAME) ? PROXY_NAME : PROXY_URL)
. ')</label><br />'
. PHP_EOL;
} if(CUSTOM_CACHE_TIMEOUT) {
$idArg = 'arg-'
. urlencode($bridgeName)
. '-'
. urlencode('_cache_timeout');
$card .= '<label for="'
. $idArg
. '">Cache timeout in seconds : </label>'
. PHP_EOL;
$card .= '<input id="'
. $idArg
. '" type="number" value="'
. $bridge->getCacheTimeout()
. '" name="_cache_timeout" /><br />'
. PHP_EOL;
}
$card .= $getHelperButtonsFormat($formats);
} else {

View File

@@ -5,7 +5,6 @@ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockq
border: 0;
outline: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
@@ -111,3 +110,8 @@ button.backbutton, button.rss-feed {
}
img {
max-width: 100%;
}