Merge branch 'master' into kt_bridge
This commit is contained in:
commit
ca73a32545
19 changed files with 964 additions and 47 deletions
|
@ -1,6 +1,6 @@
|
||||||
![RSS-Bridge](static/logo_600px.png)
|
![RSS-Bridge](static/logo_600px.png)
|
||||||
===
|
===
|
||||||
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE) [![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg?logo=github)](https://github.com/rss-bridge/rss-bridge/releases/latest) [![Debian Release](https://img.shields.io/badge/dynamic/json.svg?logo=debian&label=debian%20release&url=https%3A%2F%2Fsources.debian.org%2Fapi%2Fsrc%2Frss-bridge%2F&query=%24.versions%5B0%5D.version&colorB=blue)](https://tracker.debian.org/pkg/rss-bridge) [![Guix Release](https://img.shields.io/badge/guix%20release-unknown-blue.svg)](https://www.gnu.org/software/guix/packages/R/) [![Build Status](https://travis-ci.org/RSS-Bridge/rss-bridge.svg?branch=master)](https://travis-ci.org/RSS-Bridge/rss-bridge) [![Docker Build Status](https://img.shields.io/docker/build/rssbridge/rss-bridge.svg?logo=docker)](https://hub.docker.com/r/rssbridge/rss-bridge/)
|
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE) [![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg?logo=github)](https://github.com/rss-bridge/rss-bridge/releases/latest) [![Debian Release](https://img.shields.io/badge/dynamic/json.svg?logo=debian&label=debian%20release&url=https%3A%2F%2Fsources.debian.org%2Fapi%2Fsrc%2Frss-bridge%2F&query=%24.versions%5B0%5D.version&colorB=blue)](https://tracker.debian.org/pkg/rss-bridge) [![Guix Release](https://img.shields.io/badge/guix%20release-unknown-blue.svg)](https://www.gnu.org/software/guix/packages/R/) [![Actions Status](https://img.shields.io/github/workflow/status/RSS-Bridge/rss-bridge/Tests/master?label=GitHub%20Actions&logo=github)](https://github.com/RSS-Bridge/rss-bridge/actions) [![Docker Build Status](https://img.shields.io/docker/cloud/build/rssbridge/rss-bridge?logo=docker)](https://hub.docker.com/r/rssbridge/rss-bridge/)
|
||||||
|
|
||||||
RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one. It can be used on webservers or as a stand-alone application in CLI mode.
|
RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one. It can be used on webservers or as a stand-alone application in CLI mode.
|
||||||
|
|
||||||
|
|
219
bridges/BukowskisBridge.php
Executable file
219
bridges/BukowskisBridge.php
Executable file
|
@ -0,0 +1,219 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class BukowskisBridge extends BridgeAbstract
|
||||||
|
{
|
||||||
|
const NAME = 'Bukowskis';
|
||||||
|
const URI = 'https://www.bukowskis.com';
|
||||||
|
const DESCRIPTION = 'Fetches info about auction objects from Bukowskis auction house';
|
||||||
|
const MAINTAINER = 'Qluxzz';
|
||||||
|
const PARAMETERS = array(array(
|
||||||
|
'category' => array(
|
||||||
|
'name' => 'Category',
|
||||||
|
'type' => 'list',
|
||||||
|
'values' => array(
|
||||||
|
'All categories' => '',
|
||||||
|
'Art' => array(
|
||||||
|
'All' => 'art',
|
||||||
|
'Classic Art' => 'art.classic-art',
|
||||||
|
'Classic Finnish Art' => 'art.classic-finnish-art',
|
||||||
|
'Classic Swedish Art' => 'art.classic-swedish-art',
|
||||||
|
'Contemporary' => 'art.contemporary',
|
||||||
|
'Modern Finnish Art' => 'art.modern-finnish-art',
|
||||||
|
'Modern International Art' => 'art.modern-international-art',
|
||||||
|
'Modern Swedish Art' => 'art.modern-swedish-art',
|
||||||
|
'Old Masters' => 'art.old-masters',
|
||||||
|
'Other' => 'art.other',
|
||||||
|
'Photographs' => 'art.photographs',
|
||||||
|
'Prints' => 'art.prints',
|
||||||
|
'Sculpture' => 'art.sculpture',
|
||||||
|
'Swedish Old Masters' => 'art.swedish-old-masters',
|
||||||
|
),
|
||||||
|
'Asian Ceramics & Works of Art' => array(
|
||||||
|
'All' => 'asian-ceramics-works-of-art',
|
||||||
|
'Other' => 'asian-ceramics-works-of-art.other',
|
||||||
|
'Porcelain' => 'asian-ceramics-works-of-art.porcelain',
|
||||||
|
),
|
||||||
|
'Books & Manuscripts' => array(
|
||||||
|
'All' => 'books-manuscripts',
|
||||||
|
'Books' => 'books-manuscripts.books',
|
||||||
|
),
|
||||||
|
'Carpets, rugs & textiles' => array(
|
||||||
|
'All' => 'carpets-rugs-textiles',
|
||||||
|
'European' => 'carpets-rugs-textiles.european',
|
||||||
|
'Oriental' => 'carpets-rugs-textiles.oriental',
|
||||||
|
'Rest of the world' => 'carpets-rugs-textiles.rest-of-the-world',
|
||||||
|
'Scandinavian' => 'carpets-rugs-textiles.scandinavian',
|
||||||
|
),
|
||||||
|
'Ceramics & porcelain' => array(
|
||||||
|
'All' => 'ceramics-porcelain',
|
||||||
|
'Ceramic ware' => 'ceramics-porcelain.ceramic-ware',
|
||||||
|
'European' => 'ceramics-porcelain.european',
|
||||||
|
'Rest of the world' => 'ceramics-porcelain.rest-of-the-world',
|
||||||
|
'Scandinavian' => 'ceramics-porcelain.scandinavian',
|
||||||
|
),
|
||||||
|
'Collectibles' => array(
|
||||||
|
'All' => 'collectibles',
|
||||||
|
'Advertising & Retail' => 'collectibles.advertising-retail',
|
||||||
|
'Memorabilia' => 'collectibles.memorabilia',
|
||||||
|
'Movies & music' => 'collectibles.movies-music',
|
||||||
|
'Other' => 'collectibles.other',
|
||||||
|
'Retro & Popular Culture' => 'collectibles.retro-popular-culture',
|
||||||
|
'Technica & Nautica' => 'collectibles.technica-nautica',
|
||||||
|
'Toys' => 'collectibles.toys',
|
||||||
|
),
|
||||||
|
'Design' => array(
|
||||||
|
'All' => 'design',
|
||||||
|
'Art glass' => 'design.art-glass',
|
||||||
|
'Furniture' => 'design.furniture',
|
||||||
|
'Other' => 'design.other',
|
||||||
|
),
|
||||||
|
'Folk art' => array(
|
||||||
|
'All' => 'folk-art',
|
||||||
|
'All categories' => 'lots',
|
||||||
|
),
|
||||||
|
'Furniture' => array(
|
||||||
|
'All' => 'furniture',
|
||||||
|
'Armchairs & Sofas' => 'furniture.armchairs-sofas',
|
||||||
|
'Cabinets & Bureaus' => 'furniture.cabinets-bureaus',
|
||||||
|
'Chairs' => 'furniture.chairs',
|
||||||
|
'Garden furniture' => 'furniture.garden-furniture',
|
||||||
|
'Mirrors' => 'furniture.mirrors',
|
||||||
|
'Other' => 'furniture.other',
|
||||||
|
'Shelves & Book cases' => 'furniture.shelves-book-cases',
|
||||||
|
'Tables' => 'furniture.tables',
|
||||||
|
),
|
||||||
|
'Glassware' => array(
|
||||||
|
'All' => 'glassware',
|
||||||
|
'Glassware' => 'glassware.glassware',
|
||||||
|
'Other' => 'glassware.other',
|
||||||
|
),
|
||||||
|
'Jewellery' => array(
|
||||||
|
'All' => 'jewellery',
|
||||||
|
'Bracelets' => 'jewellery.bracelets',
|
||||||
|
'Brooches' => 'jewellery.brooches',
|
||||||
|
'Earrings' => 'jewellery.earrings',
|
||||||
|
'Necklaces & Pendants' => 'jewellery.necklaces-pendants',
|
||||||
|
'Other' => 'jewellery.other',
|
||||||
|
'Rings' => 'jewellery.rings',
|
||||||
|
),
|
||||||
|
'Lighting' => array(
|
||||||
|
'All' => 'lighting',
|
||||||
|
'Candle sticks & Candelabras' => 'lighting.candle-sticks-candelabras',
|
||||||
|
'Ceiling lights' => 'lighting.ceiling-lights',
|
||||||
|
'Chandeliers' => 'lighting.chandeliers',
|
||||||
|
'Floor lights' => 'lighting.floor-lights',
|
||||||
|
'Other' => 'lighting.other',
|
||||||
|
'Table lights' => 'lighting.table-lights',
|
||||||
|
'Wall lights' => 'lighting.wall-lights',
|
||||||
|
),
|
||||||
|
'Militaria' => array(
|
||||||
|
'All' => 'militaria',
|
||||||
|
'Honors & Medals' => 'militaria.honors-medals',
|
||||||
|
'Other militaria' => 'militaria.other-militaria',
|
||||||
|
'Weaponry' => 'militaria.weaponry',
|
||||||
|
),
|
||||||
|
'Miscellaneous' => array(
|
||||||
|
'All' => 'miscellaneous',
|
||||||
|
'Brass, Copper & Pewter' => 'miscellaneous.brass-copper-pewter',
|
||||||
|
'Nickel silver' => 'miscellaneous.nickel-silver',
|
||||||
|
'Oriental' => 'miscellaneous.oriental',
|
||||||
|
'Other' => 'miscellaneous.other',
|
||||||
|
),
|
||||||
|
'Silver' => array(
|
||||||
|
'All' => 'silver',
|
||||||
|
'Candle sticks' => 'silver.candle-sticks',
|
||||||
|
'Cups & Bowls' => 'silver.cups-bowls',
|
||||||
|
'Cutlery' => 'silver.cutlery',
|
||||||
|
'Other' => 'silver.other',
|
||||||
|
),
|
||||||
|
'Timepieces' => array(
|
||||||
|
'All' => 'timepieces',
|
||||||
|
'Other' => 'timepieces.other',
|
||||||
|
'Pocket watches' => 'timepieces.pocket-watches',
|
||||||
|
'Table clocks' => 'timepieces.table-clocks',
|
||||||
|
'Wrist watches' => 'timepieces.wrist-watches',
|
||||||
|
),
|
||||||
|
'Vintage & Fashion' => array(
|
||||||
|
'All' => 'vintage-fashion',
|
||||||
|
'Accessories' => 'vintage-fashion.accessories',
|
||||||
|
'Bags & Trunks' => 'vintage-fashion.bags-trunks',
|
||||||
|
'Clothes' => 'vintage-fashion.clothes',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'sort_order' => array(
|
||||||
|
'name' => 'Sort order',
|
||||||
|
'type' => 'list',
|
||||||
|
'values' => array(
|
||||||
|
'Ending soon' => 'ending',
|
||||||
|
'Most recent' => 'recent',
|
||||||
|
'Most bids' => 'most',
|
||||||
|
'Fewest bids' => 'fewest',
|
||||||
|
'Lowest price' => 'lowest',
|
||||||
|
'Highest price' => 'highest',
|
||||||
|
'Lowest estimate' => 'low',
|
||||||
|
'Highest estimate' => 'high',
|
||||||
|
'Alphabetical' => 'alphabetical',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'language' => array(
|
||||||
|
'name' => 'Language',
|
||||||
|
'type' => 'list',
|
||||||
|
'values' => array(
|
||||||
|
'English' => 'en',
|
||||||
|
'Swedish' => 'sv',
|
||||||
|
'Finnish' => 'fi'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||||
|
|
||||||
|
private $title;
|
||||||
|
|
||||||
|
public function collectData()
|
||||||
|
{
|
||||||
|
$baseUrl = 'https://www.bukowskis.com';
|
||||||
|
$category = $this->getInput('category');
|
||||||
|
$language = $this->getInput('language');
|
||||||
|
$sort_order = $this->getInput('sort_order');
|
||||||
|
|
||||||
|
$url = $baseUrl . '/' . $language . '/lots';
|
||||||
|
|
||||||
|
if ($category)
|
||||||
|
$url = $url . '/category/' . $category;
|
||||||
|
|
||||||
|
if ($sort_order)
|
||||||
|
$url = $url . '/sort/' . $sort_order;
|
||||||
|
|
||||||
|
$html = getSimpleHTMLDOM($url)
|
||||||
|
or returnServerError('Could not request: ' . $url);
|
||||||
|
|
||||||
|
$this->title = htmlspecialchars_decode($html->find('title', 0)->innertext);
|
||||||
|
|
||||||
|
foreach ($html->find('div.c-lot-index-lot') as $lot) {
|
||||||
|
$title = $lot->find('a.c-lot-index-lot__title', 0)->plaintext;
|
||||||
|
$relative_url = $lot->find('a.c-lot-index-lot__link', 0)->href;
|
||||||
|
$images = json_decode(
|
||||||
|
htmlspecialchars_decode(
|
||||||
|
$lot
|
||||||
|
->find('img.o-aspect-ratio__image', 0)
|
||||||
|
->getAttribute('data-thumbnails')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->items[] = array(
|
||||||
|
'title' => $title,
|
||||||
|
'uri' => $baseUrl . $relative_url,
|
||||||
|
'uid' => $lot->getAttribute('data-lot-id'),
|
||||||
|
'content' => count($images) > 0 ? "<img src='$images[0]'/><br/>$title" : $title,
|
||||||
|
'enclosures' => array_slice($images, 1),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->title ?: parent::getName();
|
||||||
|
}
|
||||||
|
}
|
143
bridges/DockerHubBridge.php
Normal file
143
bridges/DockerHubBridge.php
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
<?php
|
||||||
|
class DockerHubBridge extends BridgeAbstract {
|
||||||
|
const NAME = 'Docker Hub Bridge';
|
||||||
|
const URI = 'https://hub.docker.com';
|
||||||
|
const DESCRIPTION = 'Returns new images for a container';
|
||||||
|
const MAINTAINER = 'VerifiedJoseph';
|
||||||
|
const PARAMETERS = array(
|
||||||
|
'User Submitted Image' => array(
|
||||||
|
'user' => array(
|
||||||
|
'name' => 'User',
|
||||||
|
'type' => 'text',
|
||||||
|
'required' => true,
|
||||||
|
'exampleValue' => 'rssbridge',
|
||||||
|
),
|
||||||
|
'repo' => array(
|
||||||
|
'name' => 'Repository',
|
||||||
|
'type' => 'text',
|
||||||
|
'required' => true,
|
||||||
|
'exampleValue' => 'rss-bridge',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'Official Image' => array(
|
||||||
|
'repo' => array(
|
||||||
|
'name' => 'Repository',
|
||||||
|
'type' => 'text',
|
||||||
|
'required' => true,
|
||||||
|
'exampleValue' => 'postgres',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||||
|
|
||||||
|
private $apiURL = 'https://hub.docker.com/v2/repositories/';
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
$json = getContents($this->getApiUrl())
|
||||||
|
or returnServerError('Could not request: ' . $this->getURI());
|
||||||
|
|
||||||
|
$data = json_decode($json, false);
|
||||||
|
|
||||||
|
foreach ($data->results as $result) {
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
$lastPushed = date('Y-m-d H:i:s', strtotime($result->tag_last_pushed));
|
||||||
|
|
||||||
|
$item['title'] = $result->name;
|
||||||
|
$item['uid'] = $result->id;
|
||||||
|
$item['uri'] = $this->getTagUrl($result->name);
|
||||||
|
$item['author'] = $result->last_updater_username;
|
||||||
|
$item['timestamp'] = $result->tag_last_pushed;
|
||||||
|
$item['content'] = <<<EOD
|
||||||
|
<Strong>Tag</strong><br>
|
||||||
|
<p>{$result->name}</p>
|
||||||
|
<Strong>Last pushed</strong><br>
|
||||||
|
<p>{$lastPushed}</p>
|
||||||
|
<Strong>Images</strong><br>
|
||||||
|
{$this->getImages($result)}
|
||||||
|
EOD;
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getURI() {
|
||||||
|
if ($this->queriedContext === 'Official Image') {
|
||||||
|
return self::URI . '/_/' . $this->getRepo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->getInput('repo')) {
|
||||||
|
return self::URI . '/r/' . $this->getRepo();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName() {
|
||||||
|
if ($this->getInput('repo')) {
|
||||||
|
return $this->getRepo() . ' - Docker Hub';
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getRepo() {
|
||||||
|
if ($this->queriedContext === 'Official Image') {
|
||||||
|
return $this->getInput('repo');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getInput('user') . '/' . $this->getInput('repo');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getApiUrl() {
|
||||||
|
if ($this->queriedContext === 'Official Image') {
|
||||||
|
return $this->apiURL . 'library/' . $this->getRepo() . '/tags/?page_size=25&page=1';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->apiURL . $this->getRepo() . '/tags/?page_size=25&page=1';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getLayerUrl($name, $digest) {
|
||||||
|
if ($this->queriedContext === 'Official Image') {
|
||||||
|
return self::URI . '/layers/' . $this->getRepo() . '/library/' .
|
||||||
|
$this->getRepo() . '/' . $name . '/images/' . $digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::URI . '/layers/' . $this->getRepo() . '/' . $name . '/images/' . $digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTagUrl($name) {
|
||||||
|
if ($this->queriedContext === 'Official Image') {
|
||||||
|
return self::URI . '/_/' . $this->getRepo() . '?tab=tags&name=' . $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::URI . '/r/' . $this->getRepo() . '/tags?name=' . $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getImages($result) {
|
||||||
|
$html = <<<EOD
|
||||||
|
<table style="width:300px;"><thead><tr><th>Digest</th><th>OS/architecture</th></tr></thead></tbody>
|
||||||
|
EOD;
|
||||||
|
|
||||||
|
foreach ($result->images as $image) {
|
||||||
|
$layersUrl = $this->getLayerUrl($result->name, $image->digest);
|
||||||
|
$id = $this->getShortDigestId($image->digest);
|
||||||
|
|
||||||
|
$html .= <<<EOD
|
||||||
|
<tr>
|
||||||
|
<td><a href="{$layersUrl}">{$id}</a></td>
|
||||||
|
<td>{$image->os}/{$image->architecture}</td>
|
||||||
|
</tr>
|
||||||
|
EOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html . '</tbody></table>';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getShortDigestId($digest) {
|
||||||
|
$parts = explode(':', $digest);
|
||||||
|
return substr($parts[1], 0, 12);
|
||||||
|
}
|
||||||
|
}
|
115
bridges/FSecureBlogBridge.php
Normal file
115
bridges/FSecureBlogBridge.php
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FSecureBlogBridge extends BridgeAbstract {
|
||||||
|
const NAME = 'F-Secure Blog';
|
||||||
|
const URI = 'https://blog.f-secure.com';
|
||||||
|
const DESCRIPTION = 'F-Secure Blog';
|
||||||
|
const MAINTAINER = 'simon816';
|
||||||
|
const PARAMETERS = array(
|
||||||
|
'' => array(
|
||||||
|
'categories' => array(
|
||||||
|
'name' => 'Blog categories',
|
||||||
|
'exampleValue' => 'home-security',
|
||||||
|
),
|
||||||
|
'language' => array(
|
||||||
|
'name' => 'Language',
|
||||||
|
'defaultValue' => 'en',
|
||||||
|
),
|
||||||
|
'oldest_date' => array(
|
||||||
|
'name' => 'Oldest article date',
|
||||||
|
'exampleValue' => '-2 months',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
public function getURI() {
|
||||||
|
$lang = $this->getInput('language') or 'en';
|
||||||
|
if ($lang === 'en') {
|
||||||
|
return self::URI;
|
||||||
|
}
|
||||||
|
return self::URI . "/$lang";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
$this->items = array();
|
||||||
|
$this->seen = array();
|
||||||
|
|
||||||
|
$this->oldest = strtotime($this->getInput('oldest_date')) ?: 0;
|
||||||
|
|
||||||
|
$categories = $this->getInput('categories');
|
||||||
|
if (!empty($categories)) {
|
||||||
|
foreach (explode(',', $categories) as $cat) {
|
||||||
|
if (!empty($cat)) {
|
||||||
|
$this->collectCategory($cat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = getSimpleHTMLDOMCached($this->getURI() . '/');
|
||||||
|
|
||||||
|
foreach ($html->find('ul.c-header-menu-desktop__list li a') as $link) {
|
||||||
|
$url = parse_url($link->href);
|
||||||
|
if (($pos = strpos($url['path'], '/category/')) !== false) {
|
||||||
|
$cat = substr($url['path'], $pos + strlen('/category/'), -1);
|
||||||
|
$this->collectCategory($cat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function collectCategory($category) {
|
||||||
|
$url = $this->getURI() . "/category/$category/";
|
||||||
|
while ($url) {
|
||||||
|
$url = $this->collectListing($url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// n.b. this relies on articles to be ordered by date so the cutoff works
|
||||||
|
private function collectListing($url) {
|
||||||
|
$html = getSimpleHTMLDOMCached($url, 60 * 60);
|
||||||
|
$items = $html->find('section.b-blog .l-blog__content__listing div.c-listing-item');
|
||||||
|
|
||||||
|
$catName = trim($html->find('section.b-blog .c-blog-header__title', 0)->plaintext);
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$url = $item->getAttribute('data-url');
|
||||||
|
if (!$this->collectArticle($url)) {
|
||||||
|
return null; // Too old, stop collecting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point's to 404 for non-english blog
|
||||||
|
// $next = $html->find('link[rel=next]', 0);
|
||||||
|
$next = $html->find('ul.page-numbers a.next', 0);
|
||||||
|
return $next ? $next->href : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a boolean whether to continue collecting articles
|
||||||
|
// i.e. date is after oldest cutoff
|
||||||
|
private function collectArticle($url) {
|
||||||
|
if (array_key_exists($url, $this->seen)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$html = getSimpleHTMLDOMCached($url);
|
||||||
|
|
||||||
|
$rssItem = array( 'uri' => $url, 'uid' => $url );
|
||||||
|
$rssItem['title'] = $html->find('meta[property=og:title]', 0)->content;
|
||||||
|
$dt = $html->find('meta[property=article:published_time]', 0)->content;
|
||||||
|
// Exit if too old
|
||||||
|
if (strtotime($dt) < $this->oldest) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$rssItem['timestamp'] = $dt;
|
||||||
|
$img = $html->find('meta[property=og:image]', 0);
|
||||||
|
$rssItem['enclosures'] = $img ? array($img->content) : array();
|
||||||
|
$rssItem['author'] = trim($html->find('.c-blog-author__text a', 0)->plaintext);
|
||||||
|
$rssItem['categories'] = array_map(function ($link) {
|
||||||
|
return trim($link->plaintext);
|
||||||
|
}, $html->find('.b-single-header__categories .c-category-list a'));
|
||||||
|
$rssItem['content'] = trim($html->find('article', 0)->innertext);
|
||||||
|
|
||||||
|
$this->items[] = $rssItem;
|
||||||
|
$this->seen[$url] = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
90
bridges/FirefoxAddonsBridge.php
Normal file
90
bridges/FirefoxAddonsBridge.php
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
class FirefoxAddonsBridge extends BridgeAbstract {
|
||||||
|
const NAME = 'Firefox Add-ons Bridge';
|
||||||
|
const URI = 'https://addons.mozilla.org/';
|
||||||
|
const DESCRIPTION = 'Returns version history for a Firefox Add-on.';
|
||||||
|
const MAINTAINER = 'VerifiedJoseph';
|
||||||
|
const PARAMETERS = array(array(
|
||||||
|
'id' => array(
|
||||||
|
'name' => 'Add-on ID',
|
||||||
|
'type' => 'text',
|
||||||
|
'required' => true,
|
||||||
|
'exampleValue' => 'save-to-the-wayback-machine',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const CACHE_TIMEOUT = 3600;
|
||||||
|
|
||||||
|
private $feedName = '';
|
||||||
|
private $releaseDateRegex = '/Released ([\w, ]+) - ([\w. ]+)/';
|
||||||
|
private $xpiFileRegex = '/([A-Za-z0-9_.-]+)\.xpi$/';
|
||||||
|
private $outgoingRegex = '/https:\/\/outgoing.prod.mozaws.net\/v1\/(?:[A-z0-9]+)\//';
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
$html = getSimpleHTMLDOM($this->getURI())
|
||||||
|
or returnServerError('Could not request: ' . $this->getURI());
|
||||||
|
|
||||||
|
$this->feedName = $html->find('h1[class="AddonTitle"] > a', 0)->innertext;
|
||||||
|
$author = $html->find('span.AddonTitle-author > a', 0)->plaintext;
|
||||||
|
|
||||||
|
foreach ($html->find('div.AddonVersionCard-content') as $div) {
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
$item['title'] = $div->find('h2.AddonVersionCard-version', 0)->plaintext;
|
||||||
|
$item['uri'] = $this->getURI();
|
||||||
|
$item['author'] = $author;
|
||||||
|
|
||||||
|
if (preg_match($this->releaseDateRegex, $div->find('div.AddonVersionCard-fileInfo', 0)->plaintext, $match)) {
|
||||||
|
$item['timestamp'] = $match[1];
|
||||||
|
$size = $match[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
$compatibility = $div->find('div.AddonVersionCard-compatibility', 0)->plaintext;
|
||||||
|
$license = $div->find('p.AddonVersionCard-license', 0)->innertext;
|
||||||
|
$downloadlink = $div->find('a.InstallButtonWrapper-download-link', 0)->href;
|
||||||
|
$releaseNotes = $this->removeOutgoinglink($div->find('div.AddonVersionCard-releaseNotes', 0));
|
||||||
|
|
||||||
|
if (preg_match($this->xpiFileRegex, $downloadlink, $match)) {
|
||||||
|
$xpiFilename = $match[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$item['content'] = <<<EOD
|
||||||
|
<strong>Release Notes</strong>
|
||||||
|
<p>{$releaseNotes}</p>
|
||||||
|
<strong>Compatibility</strong>
|
||||||
|
<p>{$compatibility}</p>
|
||||||
|
<strong>License</strong>
|
||||||
|
<p>{$license}</p>
|
||||||
|
<strong>Download</strong>
|
||||||
|
<p><a href="{$downloadlink}">{$xpiFilename}</a> ($size)</p>
|
||||||
|
EOD;
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getURI() {
|
||||||
|
if (!is_null($this->getInput('id'))) {
|
||||||
|
return self::URI . 'en-US/firefox/addon/' . $this->getInput('id') . '/versions/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName() {
|
||||||
|
if (!empty($this->feedName)) {
|
||||||
|
return $this->feedName . ' - Firefox Add-on';
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeOutgoinglink($html) {
|
||||||
|
foreach ($html->find('a') as $a) {
|
||||||
|
$a->href = urldecode(preg_replace($this->outgoingRegex, '', $a->href));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html->innertext;
|
||||||
|
}
|
||||||
|
}
|
|
@ -352,12 +352,14 @@ class LeBonCoinBridge extends BridgeAbstract {
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
|
|
||||||
$url = 'https://api.leboncoin.fr/finder/search/';
|
$url = 'https://api.leboncoin.fr/api/adfinder/v1/search';
|
||||||
$data = $this->buildRequestJson();
|
$data = $this->buildRequestJson();
|
||||||
|
|
||||||
$header = array(
|
$header = array(
|
||||||
'User-Agent: LBC;Android;Null;Null;Null;Null;Null;Null;Null;Null',
|
'User-Agent: LBC;Android;10;SAMSUNG;phone;0aaaaaaaaaaaaaaa;wifi;8.24.3.8;152437;0',
|
||||||
'Content-Type: application/json',
|
'Content-Type: application/json',
|
||||||
|
'X-LBC-CC: 7',
|
||||||
|
'Accept: application/json,application/hal+json',
|
||||||
'Content-Length: ' . strlen($data),
|
'Content-Length: ' . strlen($data),
|
||||||
'api_key: ' . self::$LBC_API_KEY
|
'api_key: ' . self::$LBC_API_KEY
|
||||||
);
|
);
|
||||||
|
|
|
@ -26,7 +26,7 @@ class NordbayernBridge extends BridgeAbstract {
|
||||||
'Gunzenhausen' => 'gunzenhausen',
|
'Gunzenhausen' => 'gunzenhausen',
|
||||||
'Hersbruck' => 'hersbruck',
|
'Hersbruck' => 'hersbruck',
|
||||||
'Herzogenaurach' => 'herzogenaurach',
|
'Herzogenaurach' => 'herzogenaurach',
|
||||||
'Hilpolstein' => 'holpolstein',
|
'Hilpoltstein' => 'hilpoltstein',
|
||||||
'Höchstadt' => 'hoechstadt',
|
'Höchstadt' => 'hoechstadt',
|
||||||
'Lauf' => 'lauf',
|
'Lauf' => 'lauf',
|
||||||
'Neumarkt' => 'neumarkt',
|
'Neumarkt' => 'neumarkt',
|
||||||
|
|
|
@ -25,7 +25,7 @@ class RadioMelodieBridge extends BridgeAbstract {
|
||||||
$picture = array();
|
$picture = array();
|
||||||
|
|
||||||
// Get the Main picture URL
|
// Get the Main picture URL
|
||||||
$picture[] = $this->rewriteImage($article->find('div[id=pictureTitleSupport]', 0)->find('img', 0)->src);
|
$picture[] = self::URI . $article->find('div[id=pictureTitleSupport]', 0)->find('img', 0)->src;
|
||||||
$audioHTML = $article->find('audio');
|
$audioHTML = $article->find('audio');
|
||||||
|
|
||||||
// Add the audio element to the enclosure
|
// Add the audio element to the enclosure
|
||||||
|
|
|
@ -23,6 +23,19 @@ class RedditBridge extends BridgeAbstract {
|
||||||
'exampleValue' => 'selfhosted, php',
|
'exampleValue' => 'selfhosted, php',
|
||||||
'title' => 'SubReddit names, separated by commas'
|
'title' => 'SubReddit names, separated by commas'
|
||||||
)
|
)
|
||||||
|
),
|
||||||
|
'user' => array(
|
||||||
|
'u' => array(
|
||||||
|
'name' => 'User',
|
||||||
|
'required' => true,
|
||||||
|
'title' => 'User name'
|
||||||
|
),
|
||||||
|
'comments' => array(
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'name' => 'Comments',
|
||||||
|
'title' => 'Whether to return comments',
|
||||||
|
'defaultValue' => false
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -33,12 +46,18 @@ class RedditBridge extends BridgeAbstract {
|
||||||
public function getName() {
|
public function getName() {
|
||||||
if ($this->queriedContext == 'single') {
|
if ($this->queriedContext == 'single') {
|
||||||
return 'Reddit r/' . $this->getInput('r');
|
return 'Reddit r/' . $this->getInput('r');
|
||||||
|
} elseif ($this->queriedContext == 'user') {
|
||||||
|
return 'Reddit u/' . $this->getInput('u');
|
||||||
} else {
|
} else {
|
||||||
return self::NAME;
|
return self::NAME;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function collectData() {
|
public function collectData() {
|
||||||
|
|
||||||
|
$user = false;
|
||||||
|
$comments = false;
|
||||||
|
|
||||||
switch ($this->queriedContext) {
|
switch ($this->queriedContext) {
|
||||||
case 'single':
|
case 'single':
|
||||||
$subreddits[] = $this->getInput('r');
|
$subreddits[] = $this->getInput('r');
|
||||||
|
@ -46,33 +65,55 @@ class RedditBridge extends BridgeAbstract {
|
||||||
case 'multi':
|
case 'multi':
|
||||||
$subreddits = explode(',', $this->getInput('rs'));
|
$subreddits = explode(',', $this->getInput('rs'));
|
||||||
break;
|
break;
|
||||||
|
case 'user':
|
||||||
|
$subreddits[] = $this->getInput('u');
|
||||||
|
$user = true;
|
||||||
|
$comments = $this->getInput('comments');
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($subreddits as $subreddit) {
|
foreach ($subreddits as $subreddit) {
|
||||||
$name = trim($subreddit);
|
$name = trim($subreddit);
|
||||||
|
|
||||||
$values = getContents(self::URI . '/r/' . $name . '.json')
|
$values = getContents(self::URI . ($user ? '/user/' : '/r/') . $name . '.json')
|
||||||
or returnServerError('Unable to fetch posts!');
|
or returnServerError('Unable to fetch posts!');
|
||||||
$decodedValues = json_decode($values);
|
$decodedValues = json_decode($values);
|
||||||
|
|
||||||
foreach ($decodedValues->data->children as $post) {
|
foreach ($decodedValues->data->children as $post) {
|
||||||
|
if ($post->kind == 't1' && !$comments) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$data = $post->data;
|
$data = $post->data;
|
||||||
|
|
||||||
$item = array();
|
$item = array();
|
||||||
$item['author'] = $data->author;
|
$item['author'] = $data->author;
|
||||||
$item['title'] = $data->title;
|
|
||||||
$item['uid'] = $data->id;
|
$item['uid'] = $data->id;
|
||||||
$item['timestamp'] = $data->created_utc;
|
$item['timestamp'] = $data->created_utc;
|
||||||
$item['uri'] = $this->encodePermalink($data->permalink);
|
$item['uri'] = $this->encodePermalink($data->permalink);
|
||||||
|
|
||||||
$item['categories'] = array();
|
$item['categories'] = array();
|
||||||
|
|
||||||
|
if ($post->kind == 't1') {
|
||||||
|
$item['title'] = 'Comment: ' . $data->link_title;
|
||||||
|
} else {
|
||||||
|
$item['title'] = $data->title;
|
||||||
|
|
||||||
$item['categories'][] = $data->link_flair_text;
|
$item['categories'][] = $data->link_flair_text;
|
||||||
$item['categories'][] = $data->pinned ? 'Pinned' : null;
|
$item['categories'][] = $data->pinned ? 'Pinned' : null;
|
||||||
$item['categories'][] = $data->over_18 ? 'NSFW' : null;
|
|
||||||
$item['categories'][] = $data->spoiler ? 'Spoiler' : null;
|
$item['categories'][] = $data->spoiler ? 'Spoiler' : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$item['categories'][] = $data->over_18 ? 'NSFW' : null;
|
||||||
$item['categories'] = array_filter($item['categories']);
|
$item['categories'] = array_filter($item['categories']);
|
||||||
|
|
||||||
if ($data->is_self) {
|
if ($post->kind == 't1') {
|
||||||
|
// Comment
|
||||||
|
|
||||||
|
$item['content']
|
||||||
|
= htmlspecialchars_decode($data->body_html);
|
||||||
|
|
||||||
|
} elseif ($data->is_self) {
|
||||||
// Text post
|
// Text post
|
||||||
|
|
||||||
$item['content']
|
$item['content']
|
||||||
|
@ -112,7 +153,7 @@ class RedditBridge extends BridgeAbstract {
|
||||||
$id = $media->media_id;
|
$id = $media->media_id;
|
||||||
$type = $data->media_metadata->$id->m == 'image/gif' ? 'gif' : 'u';
|
$type = $data->media_metadata->$id->m == 'image/gif' ? 'gif' : 'u';
|
||||||
$src = $data->media_metadata->$id->s->$type;
|
$src = $data->media_metadata->$id->s->$type;
|
||||||
$images[] = '<img src="' . $src . '"/>';
|
$images[] = '<figure><img src="' . $src . '"/></figure>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$item['content'] = implode('', $images);
|
$item['content'] = implode('', $images);
|
||||||
|
|
246
bridges/ReutersBridge.php
Normal file
246
bridges/ReutersBridge.php
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
<?php
|
||||||
|
class ReutersBridge extends BridgeAbstract
|
||||||
|
{
|
||||||
|
const MAINTAINER = 'hollowleviathan, spraynard, csisoap';
|
||||||
|
const NAME = 'Reuters Bridge';
|
||||||
|
const URI = 'https://reuters.com/';
|
||||||
|
const CACHE_TIMEOUT = 1800; // 30min
|
||||||
|
const DESCRIPTION = 'Returns news from Reuters';
|
||||||
|
|
||||||
|
private $feedName = self::NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wireitem types allowed in the final story output
|
||||||
|
*/
|
||||||
|
const ALLOWED_WIREITEM_TYPES = array(
|
||||||
|
'story',
|
||||||
|
'headlines'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wireitem template types allowed in the final story output
|
||||||
|
*/
|
||||||
|
const ALLOWED_TEMPLATE_TYPES = array(
|
||||||
|
'story'
|
||||||
|
);
|
||||||
|
|
||||||
|
const PARAMETERS = array(
|
||||||
|
array(
|
||||||
|
'feed' => array(
|
||||||
|
'name' => 'News Feed',
|
||||||
|
'type' => 'list',
|
||||||
|
'title' => 'Feeds from Reuters U.S/International edition',
|
||||||
|
'values' => array(
|
||||||
|
'Aerospace and Defense' => 'aerospace',
|
||||||
|
'Business' => 'business',
|
||||||
|
'China' => 'china',
|
||||||
|
'Energy' => 'energy',
|
||||||
|
'Entertainment' => 'chan:8ym8q8dl',
|
||||||
|
'Environment' => 'chan:6u4f0jgs',
|
||||||
|
'Health' => 'chan:8hw7807a',
|
||||||
|
'Lifestyle' => 'life',
|
||||||
|
'Markets' => 'markets',
|
||||||
|
'Politics' => 'politics',
|
||||||
|
'Science' => 'science',
|
||||||
|
'Special Reports' => 'special-reports',
|
||||||
|
'Sports' => 'sports',
|
||||||
|
'Tech' => 'tech',
|
||||||
|
'Top News' => 'home/topnews',
|
||||||
|
'UK' => 'chan:61leiu7j',
|
||||||
|
'USA News' => 'us',
|
||||||
|
'Wire' => 'wire',
|
||||||
|
'World' => 'world',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs an HTTP request to the Reuters API and returns decoded JSON
|
||||||
|
* in the form of an associative array
|
||||||
|
* @param string $feed_uri Parameter string to the Reuters API
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getJson($feed_uri)
|
||||||
|
{
|
||||||
|
$uri = "https://wireapi.reuters.com/v8$feed_uri";
|
||||||
|
$returned_data = getContents($uri);
|
||||||
|
return json_decode($returned_data, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes in data from Reuters Wire API and
|
||||||
|
* creates structured data in the form of a list
|
||||||
|
* of story information.
|
||||||
|
* @param array $data JSON collected from the Reuters Wire API
|
||||||
|
*/
|
||||||
|
private function processData($data)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gets a list of wire items which are groups of templates
|
||||||
|
*/
|
||||||
|
$reuters_allowed_wireitems = array_filter(
|
||||||
|
$data, function ($wireitem) {
|
||||||
|
return in_array(
|
||||||
|
$wireitem['wireitem_type'],
|
||||||
|
self::ALLOWED_WIREITEM_TYPES
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gets a list of "Templates", which is data containing a story
|
||||||
|
*/
|
||||||
|
$reuters_wireitem_templates = array_reduce(
|
||||||
|
$reuters_allowed_wireitems,
|
||||||
|
function (array $carry, array $wireitem) {
|
||||||
|
$wireitem_templates = $wireitem['templates'];
|
||||||
|
return array_merge(
|
||||||
|
$carry,
|
||||||
|
array_filter(
|
||||||
|
$wireitem_templates, function (
|
||||||
|
array $template_data
|
||||||
|
) {
|
||||||
|
return in_array(
|
||||||
|
$template_data['type'],
|
||||||
|
self::ALLOWED_TEMPLATE_TYPES
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $reuters_wireitem_templates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getArticle($feed_uri)
|
||||||
|
{
|
||||||
|
// This will make another request to API to get full detail of article and author's name.
|
||||||
|
$rawData = $this->getJson($feed_uri);
|
||||||
|
$reuters_wireitems = $rawData['wireitems'];
|
||||||
|
$processedData = $this->processData($reuters_wireitems);
|
||||||
|
|
||||||
|
$first = reset($processedData);
|
||||||
|
$article_content = $first['story']['body_items'];
|
||||||
|
$authorlist = $first['story']['authors'];
|
||||||
|
$category = $first['story']['channel']['name'];
|
||||||
|
$image_list = $first['story']['images'];
|
||||||
|
$img_placeholder = '';
|
||||||
|
|
||||||
|
foreach($image_list as $image) { // Add more image to article.
|
||||||
|
$image_url = $image['url'];
|
||||||
|
$image_caption = $image['caption'];
|
||||||
|
$img = "<img src=\"$image_url\">";
|
||||||
|
$img_caption = "<figcaption style=\"text-align: center;\"><i>$image_caption</i></figcaption>";
|
||||||
|
$figure = "<figure>$img \t $img_caption</figure>";
|
||||||
|
$img_placeholder = $img_placeholder . $figure;
|
||||||
|
}
|
||||||
|
|
||||||
|
$author = '';
|
||||||
|
$counter = 0;
|
||||||
|
foreach ($authorlist as $data) {
|
||||||
|
//Formatting author's name.
|
||||||
|
$counter++;
|
||||||
|
$name = $data['name'];
|
||||||
|
if ($counter == count($authorlist)) {
|
||||||
|
$author = $author . $name;
|
||||||
|
} else {
|
||||||
|
$author = $author . "$name, ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$description = '';
|
||||||
|
foreach ($article_content as $content) {
|
||||||
|
$data;
|
||||||
|
if(isset($content['content'])) {
|
||||||
|
$data = $content['content'];
|
||||||
|
}
|
||||||
|
switch($content['type']) {
|
||||||
|
case 'paragraph':
|
||||||
|
$description = $description . "<p>$data</p>";
|
||||||
|
break;
|
||||||
|
case 'heading':
|
||||||
|
$description = $description . "<h3>$data</h3>";
|
||||||
|
break;
|
||||||
|
case 'infographics':
|
||||||
|
$description = $description . "<img src=\"$data\">";
|
||||||
|
break;
|
||||||
|
case 'inline_items':
|
||||||
|
$item_list = $content['items'];
|
||||||
|
$description = $description . '<p>';
|
||||||
|
foreach ($item_list as $item) {
|
||||||
|
if($item['type'] == 'text') {
|
||||||
|
$description = $description . $item['content'];
|
||||||
|
} else {
|
||||||
|
$description = $description . $item['symbol'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$description = $description . '</p>';
|
||||||
|
break;
|
||||||
|
case 'p_table':
|
||||||
|
$description = $description . $content['content'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$content_detail = array(
|
||||||
|
'content' => $description,
|
||||||
|
'author' => $author,
|
||||||
|
'category' => $category,
|
||||||
|
'images' => $img_placeholder,
|
||||||
|
);
|
||||||
|
return $content_detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName() {
|
||||||
|
return $this->feedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function collectData()
|
||||||
|
{
|
||||||
|
$reuters_feed_name = $this->getInput('feed');
|
||||||
|
|
||||||
|
if(strpos($reuters_feed_name, 'chan:') !== false) {
|
||||||
|
// Now checking whether that feed has unique ID or not.
|
||||||
|
$feed_uri = "/feed/rapp/us/wirefeed/$reuters_feed_name";
|
||||||
|
} else {
|
||||||
|
$feed_uri = "/feed/rapp/us/tabbar/feeds/$reuters_feed_name";
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->getJson($feed_uri);
|
||||||
|
|
||||||
|
$reuters_wireitems = $data['wireitems'];
|
||||||
|
$this->feedName = $data['wire_name'] . ' | Reuters';
|
||||||
|
$processedData = $this->processData($reuters_wireitems);
|
||||||
|
|
||||||
|
// Merge all articles from Editor's Highlight section into existing array of templates.
|
||||||
|
$top_section = reset($reuters_wireitems);
|
||||||
|
if ($top_section['wireitem_type'] == 'headlines') {
|
||||||
|
$top_articles = $top_section['templates'][1]['headlines'];
|
||||||
|
$processedData = array_merge($top_articles, $processedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($processedData as $story) {
|
||||||
|
$item['uid'] = $story['story']['usn'];
|
||||||
|
$article_uri = $story['template_action']['api_path'];
|
||||||
|
$content_detail = $this->getArticle($article_uri);
|
||||||
|
$description = $content_detail['content'];
|
||||||
|
$author = $content_detail['author'];
|
||||||
|
$images = $content_detail['images'];
|
||||||
|
$item['categories'] = array($content_detail['category']);
|
||||||
|
$item['author'] = $author;
|
||||||
|
if (!(bool) $description) {
|
||||||
|
$description = $story['story']['lede']; // Just in case the content doesn't have anything.
|
||||||
|
} else {
|
||||||
|
$item['content'] = "$description $images";
|
||||||
|
}
|
||||||
|
|
||||||
|
$item['title'] = $story['story']['hed'];
|
||||||
|
$item['timestamp'] = $story['story']['updated_at'];
|
||||||
|
$item['uri'] = $story['template_action']['url'];
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,9 @@ class SoundCloudBridge extends BridgeAbstract {
|
||||||
private $feedIcon = null;
|
private $feedIcon = null;
|
||||||
private $clientIDCache = null;
|
private $clientIDCache = null;
|
||||||
|
|
||||||
|
private $clientIdRegex = '/client_id.*?"(.+?)"/';
|
||||||
|
private $widgetRegex = '/widget-.+?\.js/';
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
$res = $this->apiGet('resolve', array(
|
$res = $this->apiGet('resolve', array(
|
||||||
'url' => 'https://soundcloud.com/' . $this->getInput('u')
|
'url' => 'https://soundcloud.com/' . $this->getInput('u')
|
||||||
|
@ -113,21 +116,32 @@ class SoundCloudBridge extends BridgeAbstract {
|
||||||
// Without url=http, this returns a 404
|
// Without url=http, this returns a 404
|
||||||
$playerHTML = getContents('https://w.soundcloud.com/player/?url=http')
|
$playerHTML = getContents('https://w.soundcloud.com/player/?url=http')
|
||||||
or returnServerError('Unable to get player page.');
|
or returnServerError('Unable to get player page.');
|
||||||
$regex = '/widget-.+?\.js/';
|
|
||||||
if(preg_match($regex, $playerHTML, $matches) == false)
|
// Extract widget JS filenames from player page
|
||||||
|
if(preg_match_all($this->widgetRegex, $playerHTML, $matches) == false)
|
||||||
returnServerError('Unable to find widget JS URL.');
|
returnServerError('Unable to find widget JS URL.');
|
||||||
$widgetURL = 'https://widget.sndcdn.com/' . $matches[0];
|
|
||||||
|
$clientID = '';
|
||||||
|
|
||||||
|
// Loop widget js files and extract client ID
|
||||||
|
foreach ($matches[0] as $widgetFile) {
|
||||||
|
$widgetURL = 'https://widget.sndcdn.com/' . $widgetFile;
|
||||||
|
|
||||||
$widgetJS = getContents($widgetURL)
|
$widgetJS = getContents($widgetURL)
|
||||||
or returnServerError('Unable to get widget JS page.');
|
or returnServerError('Unable to get widget JS page.');
|
||||||
$regex = '/client_id.*?"(.+?)"/';
|
|
||||||
if(preg_match($regex, $widgetJS, $matches) == false)
|
|
||||||
returnServerError('Unable to find client ID.');
|
|
||||||
$clientID = $matches[1];
|
|
||||||
|
|
||||||
|
if(preg_match($this->clientIdRegex, $widgetJS, $matches)) {
|
||||||
|
$clientID = $matches[1];
|
||||||
$this->clientIDCache->saveData($clientID);
|
$this->clientIDCache->saveData($clientID);
|
||||||
|
|
||||||
return $clientID;
|
return $clientID;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($clientID)) {
|
||||||
|
returnServerError('Unable to find client ID.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function buildAPIURL($endpoint, $parameters){
|
private function buildAPIURL($endpoint, $parameters){
|
||||||
return 'https://api-v2.soundcloud.com/'
|
return 'https://api-v2.soundcloud.com/'
|
||||||
|
|
34
bridges/SymfonyCastsBridge.php
Normal file
34
bridges/SymfonyCastsBridge.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class SymfonyCastsBridge extends BridgeAbstract {
|
||||||
|
const NAME = 'SymfonyCasts Bridge';
|
||||||
|
const URI = 'https://symfonycasts.com/';
|
||||||
|
const DESCRIPTION = 'Follow new updates on symfonycasts.com';
|
||||||
|
const MAINTAINER = 'Park0';
|
||||||
|
const CACHE_TIMEOUT = 3600;
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
$html = getSimpleHTMLDOM('https://symfonycasts.com/updates/find')
|
||||||
|
or returnServerError('Unable to get page.');
|
||||||
|
$dives = $html->find('div');
|
||||||
|
|
||||||
|
/* @var simple_html_dom $div */
|
||||||
|
foreach ($dives as $div) {
|
||||||
|
$id = $div->getAttribute('data-mark-update-id-value');
|
||||||
|
$type = $div->find('h5', 0);
|
||||||
|
$title = $div->find('span', 0);
|
||||||
|
$dateString = $div->find('h5.font-gray', 0);
|
||||||
|
$href = $div->find('a', 0);
|
||||||
|
$url = 'https://symfonycasts.com' . $href->getAttribute('href');
|
||||||
|
|
||||||
|
$item = array(); // Create an empty item
|
||||||
|
$item['uid'] = $id;
|
||||||
|
$item['title'] = $title->innertext;
|
||||||
|
$item['timestamp'] = $dateString->innertext;
|
||||||
|
$item['content'] = $type->plaintext . '<a href="' . $url . '">' . $title . '</a>';
|
||||||
|
$item['uri'] = $url;
|
||||||
|
$this->items[] = $item; // Add item to the list
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,18 @@ class TelegramBridge extends BridgeAbstract {
|
||||||
private $itemTitle = '';
|
private $itemTitle = '';
|
||||||
|
|
||||||
private $backgroundImageRegex = "/background-image:url\('(.*)'\)/";
|
private $backgroundImageRegex = "/background-image:url\('(.*)'\)/";
|
||||||
|
private $detectParamsRegex = '/^https?:\/\/t.me\/(?:s\/)?([\w]+)$/';
|
||||||
|
|
||||||
|
public function detectParameters($url) {
|
||||||
|
$params = array();
|
||||||
|
|
||||||
|
if(preg_match($this->detectParamsRegex, $url, $matches) > 0) {
|
||||||
|
$params['username'] = $matches[1];
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public function collectData() {
|
public function collectData() {
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ class TheYeteeBridge extends BridgeAbstract {
|
||||||
$html = getSimpleHTMLDOM(self::URI)
|
$html = getSimpleHTMLDOM(self::URI)
|
||||||
or returnServerError('Could not request The Yetee.');
|
or returnServerError('Could not request The Yetee.');
|
||||||
|
|
||||||
$div = $html->find('.hero-col');
|
$div = $html->find('.module_timed-item.is--full');
|
||||||
foreach($div as $element) {
|
foreach($div as $element) {
|
||||||
|
|
||||||
$item = array();
|
$item = array();
|
||||||
|
@ -21,16 +21,15 @@ class TheYeteeBridge extends BridgeAbstract {
|
||||||
$title = $element->find('h2', 0)->plaintext;
|
$title = $element->find('h2', 0)->plaintext;
|
||||||
$item['title'] = $title;
|
$item['title'] = $title;
|
||||||
|
|
||||||
$author = trim($element->find('div[class=credit]', 0)->plaintext);
|
$author = trim($element->find('.module_timed-item--artist a', 0)->plaintext);
|
||||||
$item['author'] = $author;
|
$item['author'] = $author;
|
||||||
|
|
||||||
$uri = $element->find('div[class=controls] a', 0)->href;
|
$item['uri'] = static::URI;
|
||||||
$item['uri'] = static::URI . $uri;
|
|
||||||
|
|
||||||
$content = '<p>' . $element->find('section[class=product-listing-info] p', -1)->plaintext . '</p>';
|
$content = '<p>' . $title . ' by ' . $author . '</p>';
|
||||||
$photos = $element->find('a[class=js-modaal-gallery] img');
|
$photos = $element->find('a.img');
|
||||||
foreach($photos as $photo) {
|
foreach($photos as $photo) {
|
||||||
$content = $content . "<br /><img src='$photo->src' />";
|
$content = $content . "<br /><img src='$photo->href' />";
|
||||||
$item['enclosures'][] = $photo->src;
|
$item['enclosures'][] = $photo->src;
|
||||||
}
|
}
|
||||||
$item['content'] = $content;
|
$item['content'] = $content;
|
||||||
|
|
|
@ -379,6 +379,7 @@ class VkBridge extends BridgeAbstract
|
||||||
return $time;
|
return $time;
|
||||||
} else {
|
} else {
|
||||||
$strdate = $post->find('span.rel_date', 0)->plaintext;
|
$strdate = $post->find('span.rel_date', 0)->plaintext;
|
||||||
|
$strdate = preg_replace('/[\x00-\x1F\x7F-\xFF]/', ' ', $strdate);
|
||||||
|
|
||||||
$date = date_parse($strdate);
|
$date = date_parse($strdate);
|
||||||
if (!$date['year']) {
|
if (!$date['year']) {
|
||||||
|
|
|
@ -34,7 +34,7 @@ class ZoneTelechargementBridge extends BridgeAbstract {
|
||||||
);
|
);
|
||||||
|
|
||||||
// This is an URL that is not protected by robot protection for Direct Download
|
// This is an URL that is not protected by robot protection for Direct Download
|
||||||
const UNPROTECTED_URI = 'https://www.zone-annuaire.com/';
|
const UNPROTECTED_URI = 'https://www.zone-telechargement.net/';
|
||||||
|
|
||||||
// This is an URL that is not protected by robot protection for Streaming Links
|
// This is an URL that is not protected by robot protection for Streaming Links
|
||||||
const UNPROTECTED_URI_STREAMING = 'https://zone-telechargement.stream/';
|
const UNPROTECTED_URI_STREAMING = 'https://zone-telechargement.stream/';
|
||||||
|
|
|
@ -347,11 +347,13 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||||
CARD;
|
CARD;
|
||||||
|
|
||||||
// If we don't have any parameter for the bridge, we print a generic form to load it.
|
// If we don't have any parameter for the bridge, we print a generic form to load it.
|
||||||
if(count($parameters) === 0
|
if (count($parameters) === 0) {
|
||||||
|| count($parameters) === 1 && array_key_exists('global', $parameters)) {
|
|
||||||
|
|
||||||
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
|
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
|
||||||
|
|
||||||
|
// Display form with cache timeout and/or noproxy options (if enabled) when bridge has no parameters
|
||||||
|
} else if (count($parameters) === 1 && array_key_exists('global', $parameters)) {
|
||||||
|
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, '', $parameters['global']);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
foreach($parameters as $parameterName => $parameter) {
|
foreach($parameters as $parameterName => $parameter) {
|
||||||
|
|
|
@ -46,7 +46,13 @@ class FormatFactory extends FactoryAbstract {
|
||||||
throw new \InvalidArgumentException('Format name invalid!');
|
throw new \InvalidArgumentException('Format name invalid!');
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = $this->sanitizeFormatName($name) . 'Format';
|
$name = $this->sanitizeFormatName($name);
|
||||||
|
|
||||||
|
if (is_null($name)) {
|
||||||
|
throw new \InvalidArgumentException('Unknown format given!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$name .= 'Format';
|
||||||
$pathFormat = $this->getWorkingDir() . $name . '.php';
|
$pathFormat = $this->getWorkingDir() . $name . '.php';
|
||||||
|
|
||||||
if(!file_exists($pathFormat)) {
|
if(!file_exists($pathFormat)) {
|
||||||
|
@ -72,7 +78,7 @@ class FormatFactory extends FactoryAbstract {
|
||||||
* @return bool true if the name is a valid format name, false otherwise.
|
* @return bool true if the name is a valid format name, false otherwise.
|
||||||
*/
|
*/
|
||||||
public function isFormatName($name){
|
public function isFormatName($name){
|
||||||
return is_string($name) && preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name) === 1;
|
return is_string($name) && preg_match('/^[a-zA-Z0-9-]*$/', $name) === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -108,8 +114,6 @@ class FormatFactory extends FactoryAbstract {
|
||||||
* * The PHP file name without file extension (i.e. `AtomFormat`)
|
* * The PHP file name without file extension (i.e. `AtomFormat`)
|
||||||
* * The format name (i.e. `Atom`)
|
* * The format name (i.e. `Atom`)
|
||||||
*
|
*
|
||||||
* Casing is ignored (i.e. `ATOM` and `atom` are the same).
|
|
||||||
*
|
|
||||||
* A format file matching the given format name must exist in the working
|
* A format file matching the given format name must exist in the working
|
||||||
* directory!
|
* directory!
|
||||||
*
|
*
|
||||||
|
@ -118,6 +122,7 @@ class FormatFactory extends FactoryAbstract {
|
||||||
* valid, null otherwise.
|
* valid, null otherwise.
|
||||||
*/
|
*/
|
||||||
protected function sanitizeFormatName($name) {
|
protected function sanitizeFormatName($name) {
|
||||||
|
$name = ucfirst(strtolower($name));
|
||||||
|
|
||||||
if(is_string($name)) {
|
if(is_string($name)) {
|
||||||
|
|
||||||
|
@ -131,18 +136,12 @@ class FormatFactory extends FactoryAbstract {
|
||||||
$name = $matches[1];
|
$name = $matches[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Improve performance for correctly written format names
|
// The name is valid if a corresponding format file is found on disk
|
||||||
if(in_array($name, $this->getFormatNames())) {
|
if(in_array($name, $this->getFormatNames())) {
|
||||||
$index = array_search($name, $this->getFormatNames());
|
$index = array_search($name, $this->getFormatNames());
|
||||||
return $this->getFormatNames()[$index];
|
return $this->getFormatNames()[$index];
|
||||||
}
|
}
|
||||||
|
|
||||||
// The name is valid if a corresponding format file is found on disk
|
|
||||||
if(in_array(strtolower($name), array_map('strtolower', $this->getFormatNames()))) {
|
|
||||||
$index = array_search(strtolower($name), array_map('strtolower', $this->getFormatNames()));
|
|
||||||
return $this->getFormatNames()[$index];
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::log('Invalid format name: "' . $name . '"!');
|
Debug::log('Invalid format name: "' . $name . '"!');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -311,7 +311,7 @@ function getSimpleHTMLDOMCached($url,
|
||||||
$time = $cache->getTime();
|
$time = $cache->getTime();
|
||||||
if($time !== false
|
if($time !== false
|
||||||
&& (time() - $duration < $time)
|
&& (time() - $duration < $time)
|
||||||
&& Debug::isEnabled()) { // Contents within duration
|
&& !Debug::isEnabled()) { // Contents within duration
|
||||||
$content = $cache->loadData();
|
$content = $cache->loadData();
|
||||||
} else { // Content not within duration
|
} else { // Content not within duration
|
||||||
$content = getContents($url, $header, $opts);
|
$content = getContents($url, $header, $opts);
|
||||||
|
|
Loading…
Reference in a new issue