Compare commits

..

No commits in common. "master" and "2025-01-26" have entirely different histories.

32 changed files with 119 additions and 1122 deletions

View File

@ -49,9 +49,9 @@ Please describe what you expect from the bridge. Whenever possible provide sampl
- _Default limit_: 5 - _Default limit_: 5
- [ ] Load full articles - [ ] Load full articles
- _Cache articles_ (articles are stored in a local cache on first request): yes - _Cache articles_ (articles are stored in a local cache on first request): yes
- _Cache timeout_ : 24 hours - _Cache timeout_ (max = 24 hours): 24 hours
- [X] Balance requests (RSS-Bridge uses cached versions to reduce bandwith usage) - [X] Balance requests (RSS-Bridge uses cached versions to reduce bandwith usage)
- _Timeout_ (default = 5 minutes): 5 minutes - _Timeout_ (default = 5 minutes, max = 24 hours): 5 minutes
<!--Be aware that some options might not be available for your specific request due to technical limitations!--> <!--Be aware that some options might not be available for your specific request due to technical limitations!-->

View File

@ -23,7 +23,6 @@
* [Binnette](https://github.com/Binnette) * [Binnette](https://github.com/Binnette)
* [BoboTiG](https://github.com/BoboTiG) * [BoboTiG](https://github.com/BoboTiG)
* [Bockiii](https://github.com/Bockiii) * [Bockiii](https://github.com/Bockiii)
* [brtsos](https://github.com/brtsos)
* [captn3m0](https://github.com/captn3m0) * [captn3m0](https://github.com/captn3m0)
* [chemel](https://github.com/chemel) * [chemel](https://github.com/chemel)
* [Chouchen](https://github.com/Chouchen) * [Chouchen](https://github.com/Chouchen)

View File

@ -150,11 +150,11 @@ listen = /run/php/rss-bridge.sock
listen.owner = www-data listen.owner = www-data
listen.group = www-data listen.group = www-data
; Create 10 workers standing by to serve requests # Create 10 workers standing by to serve requests
pm = static pm = static
pm.max_children = 10 pm.max_children = 10
; Respawn worker after 500 requests (workaround for memory leaks etc.) # Respawn worker after 500 requests (workaround for memory leaks etc.)
pm.max_requests = 500 pm.max_requests = 500
``` ```
@ -460,6 +460,7 @@ See [CONTRIBUTORS.md](CONTRIBUTORS.md)
RSS-Bridge uses caching to prevent services from banning your server for repeatedly updating feeds. RSS-Bridge uses caching to prevent services from banning your server for repeatedly updating feeds.
The specific cache duration can be different between bridges. The specific cache duration can be different between bridges.
Cached files are deleted automatically after 24 hours.
RSS-Bridge allows you to take full control over which bridges are displayed to the user. RSS-Bridge allows you to take full control over which bridges are displayed to the user.
That way you can host your own RSS-Bridge service with your favorite collection of bridges! That way you can host your own RSS-Bridge service with your favorite collection of bridges!

View File

@ -67,7 +67,7 @@ class AnisearchBridge extends BridgeAbstract
$trailerlink = $domarticle->find('section#trailers > div > div.swiper > ul.swiper-wrapper > li.swiper-slide > a', 0); $trailerlink = $domarticle->find('section#trailers > div > div.swiper > ul.swiper-wrapper > li.swiper-slide > a', 0);
if (isset($trailerlink)) { if (isset($trailerlink)) {
$trailersite = getSimpleHTMLDOM($baseurl . $trailerlink->href); $trailersite = getSimpleHTMLDOM($baseurl . $trailerlink->href);
$trailer = $trailersite->find('div#video > iframe', 0); $trailer = $trailersite->find('div#player > iframe', 0);
$trailer = $trailer->{'data-xsrc'}; $trailer = $trailer->{'data-xsrc'};
$ytlink = <<<EOT $ytlink = <<<EOT
<br /><iframe width="560" height="315" src="$trailer" title="YouTube video player" <br /><iframe width="560" height="315" src="$trailer" title="YouTube video player"

View File

@ -1,344 +0,0 @@
<?php
class AuctionetBridge extends BridgeAbstract
{
const NAME = 'Auctionet';
const URI = 'https://www.auctionet.com';
const DESCRIPTION = 'Fetches info about auction objects from Auctionet (an auction platform for many European auction houses)';
const MAINTAINER = 'Qluxzz';
const PARAMETERS = [[
'category' => [
'name' => 'Category',
'type' => 'list',
'values' => [
'All categories' => '',
'Art' => [
'All' => '25-art',
'Drawings' => '119-drawings',
'Engravings & Prints' => '27-engravings-prints',
'Other' => '30-other',
'Paintings' => '28-paintings',
'Photography' => '26-photography',
'Sculptures & Bronzes' => '29-sculptures-bronzes',
],
'Asiatica' => [
'All' => '117-asiatica',
],
'Books, Maps & Manuscripts' => [
'All' => '50-books-maps-manuscripts',
'Autographs & Manuscripts' => '206-autographs-manuscripts',
'Books' => '204-books',
'Maps' => '205-maps',
'Other' => '207-other',
],
'Carpets & Textiles' => [
'All' => '35-carpets-textiles',
'Carpets' => '36-carpets',
'Textiles' => '37-textiles',
],
'Ceramics & Porcelain' => [
'All' => '9-ceramics-porcelain',
'European' => '10-european',
'Oriental' => '11-oriental',
'Rest of the world' => '12-rest-of-the-world',
'Tableware' => '210-tableware',
],
'Clocks & Watches' => [
'All' => '31-clocks-watches',
'Carriage & Miniature Clocks' => '258-carriage-miniature-clocks',
'Longcase clocks' => '32-longcase-clocks',
'Mantel clocks' => '33-mantel-clocks',
'Other clocks' => '34-other-clocks',
'Pocket & Stop Watches' => '110-pocket-stop-watches',
'Wall Clocks' => '127-wall-clocks',
'Wristwatches' => '15-wristwatches',
],
'Coins, Medals & Stamps' => [
'All' => '46-coins-medals-stamps',
'Coins' => '128-coins',
'Orders & Medals' => '135-orders-medals',
'Other' => '131-other',
'Stamps' => '136-stamps',
],
'Folk art' => [
'All' => '58-folk-art',
'Bowls & Boxes' => '121-bowls-boxes',
'Furniture' => '122-furniture',
'Other' => '123-other',
'Tools & Gears' => '120-tools-gears',
],
'Furniture' => [
'All' => '16-furniture',
'Armchairs & Chairs' => '18-armchairs-chairs',
'Chests of drawers' => '24-chests-of-drawers',
'Cupboards, Cabinets & Shelves' => '23-cupboards-cabinets-shelves',
'Dining room furniture' => '22-dining-room-furniture',
'Garden' => '21-garden',
'Other' => '17-other',
'Sofas & seatings' => '20-sofas-seatings',
'Tables' => '19-tables',
],
'Glass' => [
'All' => '6-glass',
'Art glass' => '208-art-glass',
'Other' => '8-other',
'Tableware' => '7-tableware',
'Utility glass' => '209-utility-glass',
],
'Jewellery & Gemstones' => [
'All' => '13-jewellery-gemstones',
'Alliance rings' => '113-alliance-rings',
'Bracelets' => '106-bracelets',
'Brooches & Pendants' => '107-brooches-pendants',
'Costume Jewellery' => '259-costume-jewellery',
'Cufflinks & Tie Pins' => '111-cufflinks-tie-pins',
'Ear studs' => '116-ear-studs',
'Earrings' => '115-earrings',
'Gemstones' => '48-gemstones',
'Jewellery' => '14-jewellery',
'Jewellery Suites' => '109-jewellery-suites',
'Necklace' => '104-necklace',
'Other' => '118-other',
'Rings' => '112-rings',
'Signet rings' => '105-signet-rings',
'Solitaire rings' => '114-solitaire-rings',
],
'Licence weapons' => [
'All' => '59-licence-weapons',
'Combi/Combo' => '63-combi-combo',
'Double express rifles' => '60-double-express-rifles',
'Rifles' => '61-rifles',
'Shotguns' => '62-shotguns',
],
'Lighting & Lamps' => [
'All' => '1-lighting-lamps',
'Candlesticks' => '4-candlesticks',
'Ceiling lights' => '3-ceiling-lights',
'Chandeliers' => '203-chandeliers',
'Floor lights' => '2-floor-lights',
'Other lighting' => '5-other-lighting',
'Table Lamps' => '125-table-lamps',
'Wall Lights' => '124-wall-lights',
],
'Mirrors' => [
'All' => '42-mirrors',
],
'Miscellaneous' => [
'All' => '43-miscellaneous',
'Fishing equipment' => '54-fishing-equipment',
'Miscellaneous' => '47-miscellaneous',
'Modern Tools' => '133-modern-tools',
'Modern consumer electronics' => '52-modern-consumer-electronics',
'Musical instruments' => '51-musical-instruments',
'Technica & Nautica' => '45-technica-nautica',
],
'Photo, Cameras & Lenses' => [
'All' => '57-photo-cameras-lenses',
'Cameras & accessories' => '71-cameras-accessories',
'Optics' => '66-optics',
'Other' => '72-other',
],
'Silver & Metals' => [
'All' => '38-silver-metals',
'Other metals' => '40-other-metals',
'Pewter, Brass & Copper' => '41-pewter-brass-copper',
'Silver' => '39-silver',
'Silver plated' => '213-silver-plated',
],
'Toys' => [
'All' => '44-toys',
'Comics' => '211-comics',
'Toys' => '212-toys',
],
'Tribal art' => [
'All' => '134-tribal-art',
],
'Vehicles, Boats & Parts' => [
'All' => '249-vehicles-boats-parts',
'Automobilia & Transport' => '255-automobilia-transport',
'Bicycles' => '132-bicycles',
'Boats & Accessories' => '250-boats-accessories',
'Car parts' => '253-car-parts',
'Cars' => '215-cars',
'Moped parts' => '254-moped-parts',
'Mopeds' => '216-mopeds',
'Motorcycle parts' => '252-motorcycle-parts',
'Motorcycles' => '251-motorcycles',
'Other' => '256-other',
],
'Vintage & Designer Fashion' => [
'All' => '49-vintage-designer-fashion',
],
'Weapons & Militaria' => [
'All' => '137-weapons-militaria',
'Airguns' => '257-airguns',
'Armour & Uniform' => '138-armour-uniform',
'Edged weapons' => '130-edged-weapons',
'Guns & Rifles' => '129-guns-rifles',
'Other' => '214-other',
],
'Wine, Port & Spirits' => [
'All' => '170-wine-port-spirits',
],
]
],
'sort_order' => [
'name' => 'Sort order',
'type' => 'list',
'values' => [
'Most bids' => 'bids_count_desc',
'Lowest bid' => 'bid_asc',
'Highest bid' => 'bid_desc',
'Last bid on' => 'bid_on',
'Ending soonest' => 'end_asc_active',
'Lowest estimate' => 'estimate_asc',
'Highest estimate' => 'estimate_desc',
'Recently added' => 'recent'
],
],
'country' => [
'name' => 'Country',
'type' => 'list',
'values' => [
'All' => '',
'Denmark' => 'DK',
'Finland' => 'FI',
'Germany' => 'DE',
'Spain' => 'ES',
'Sweden' => 'SE',
'United Kingdom' => 'GB'
]
],
'language' => [
'name' => 'Language',
'type' => 'list',
'values' => [
'English' => 'en',
'Español' => 'es',
'Deutsch' => 'de',
'Svenska' => 'sv',
'Dansk' => 'da',
'Suomi' => 'fi',
],
],
]];
const CACHE_TIMEOUT = 3600; // 1 hour
private $title;
public function collectData()
{
// Each page contains 48 auctions
// So we fetch 10 pages so we decrease the likelihood
// of missing auctions between feed refreshes
// Fetch first page and use that to get title
{
$url = $this->getUrl(1);
$data = getContents($url);
$title = $this->getDocumentTitle($data);
$this->items = array_merge($this->items, $this->parsePageData($data));
}
// Fetch remaining pages
for ($page = 2; $page <= 10; $page++) {
$url = $this->getUrl($page);
$data = getContents($url);
$this->items = array_merge($this->items, $this->parsePageData($data));
}
}
public function getName()
{
return $this->title ?: parent::getName();
}
/* HELPERS */
private function getUrl($page)
{
$category = $this->getInput('category');
$language = $this->getInput('language');
$sort_order = $this->getInput('sort_order');
$country = $this->getInput('country');
$url = self::URI . '/' . $language . '/search';
if ($category) {
$url = $url . '/' . $category;
}
$query = [];
$query['page'] = $page;
if ($sort_order) {
$query['order'] = $sort_order;
}
if ($country) {
$query['country_code'] = $country;
}
if (count($query) > 0) {
$url = $url . '?' . http_build_query($query);
}
return $url;
}
private function getDocumentTitle($data)
{
$title_elem = '<title>';
$title_elem_length = strlen($title_elem);
$title_start = strpos($data, $title_elem);
$title_end = strpos($data, '</title>', $title_start);
$title_length = $title_end - $title_start + strlen($title_elem);
$title = substr($data, $title_start + strlen($title_elem), $title_length);
return $title;
}
/**
* The auction items data is included in the HTML document
* as a HTML entities encoded JSON structure
* which is used to hydrate the React component for the list of auctions
*/
private function parsePageData($data)
{
$key = 'data-react-props="';
$keyLength = strlen($key);
$start = strpos($data, $key);
$end = strpos($data, '"', $start + strlen($key));
$length = $end - ($start + $keyLength);
$jsonString = substr($data, $start + $keyLength, $length);
$jsonData = json_decode(htmlspecialchars_decode($jsonString), false);
$items = [];
foreach ($jsonData->{'items'} as $item) {
$title = $item->{'longTitle'};
$relative_url = $item->{'url'};
$images = $item->{'imageUrls'};
$id = $item->{'auctionId'};
$items[] = [
'title' => $title,
'uri' => self::URI . $relative_url,
'uid' => $id,
'content' => count($images) > 0 ? "<img src='$images[0]'/><br/>$title" : $title,
'enclosures' => array_slice($images, 1),
];
}
return $items;
}
}

View File

@ -206,7 +206,7 @@ class BukowskisBridge extends BridgeAbstract
$this->items[] = [ $this->items[] = [
'title' => $title, 'title' => $title,
'uri' => $baseUrl . $relative_url, 'uri' => $baseUrl . $relative_url,
'uid' => $relative_url, 'uid' => $lot->getAttribute('data-lot-id'),
'content' => count($images) > 0 ? "<img src='$images[0]'/><br/>$title" : $title, 'content' => count($images) > 0 ? "<img src='$images[0]'/><br/>$title" : $title,
'enclosures' => array_slice($images, 1), 'enclosures' => array_slice($images, 1),
]; ];

View File

@ -66,7 +66,7 @@ class CarThrottleBridge extends BridgeAbstract
foreach ($categoryPage->find('div.cmg-card') as $post) { foreach ($categoryPage->find('div.cmg-card') as $post) {
$item = []; $item = [];
$titleElement = $post->find('a.title')[0]; $titleElement = $post->find('div.title a')[0];
$post_uri = self::URI . $titleElement->getAttribute('href'); $post_uri = self::URI . $titleElement->getAttribute('href');
if (!isset($post_uri) || $post_uri == '') { if (!isset($post_uri) || $post_uri == '') {
@ -80,8 +80,8 @@ class CarThrottleBridge extends BridgeAbstract
$item['author'] = $this->parseAuthor($articlePage); $item['author'] = $this->parseAuthor($articlePage);
$articleImage = $articlePage->find('figure')[0]; $articleImage = $articlePage->find('div.block-layout-field-image')[0];
$article = $articlePage->find('div.first-column div.body')[0]; $article = $articlePage->find('div.block-layout-body')[1];
//remove ads //remove ads
foreach ($article->find('aside') as $ad) { foreach ($article->find('aside') as $ad) {

View File

@ -1,152 +0,0 @@
<?php
class CybermonitBridge extends BridgeAbstract
{
const SALT = "Mc!dks\\0#)as";
const NAME = 'Cybermonit';
const DESCRIPTION = 'Returns cybermonite CVEs, DDoS, Ransomware, Leaks, EOL, Releases';
const MAINTAINER = 'void';
const PARAMETERS = [[
'feed_type' => [
'name' => 'feed_type',
'type' => 'list',
'values' => [
'Leaks' => 'Leaks',
'CVEs' => 'CVEs',
'DDoS' => 'DDoS',
'Ransomware' => 'Ransomware',
'Releases' => 'Releases',
'EOL' => "EOL"
]
]
]];
public function getURI() {
switch ($this->getKey("feed_type")) {
case 'Leaks':
return 'https://pypbbsgyhtlerxdyfuvv.supabase.co/storage/v1/object/dane/leak.json';
break;
case 'CVEs':
return 'https://pypbbsgyhtlerxdyfuvv.supabase.co/storage/v1/object/dane/2025.json';
break;
case 'DDoS':
return 'https://pypbbsgyhtlerxdyfuvv.supabase.co/storage/v1/object/dane/ddos.json';
break;
case 'Ransomware':
return 'https://pypbbsgyhtlerxdyfuvv.supabase.co/storage/v1/object/dane/ransomware.json';
break;
case 'EOL':
return 'https://pypbbsgyhtlerxdyfuvv.supabase.co/storage/v1/object/dane/eol.json';
break;
case 'Releases':
return 'https://pypbbsgyhtlerxdyfuvv.supabase.co/storage/v1/object/dane/releases.json';
break;
default:
return 'https://cybermonit.com';
break;
}
}
public function collectData()
{
$opts = array(
'accept: */*',
'apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB5cGJic2d5aHRsZXJ4ZHlmdXZ2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxMjUwNzAsImV4cCI6MjA1NTcwMTA3MH0.xmcCqJUgvRtVsp6XmaH1YmUeerqZRZNQ_XmCnpOEFAo',
'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB5cGJic2d5aHRsZXJ4ZHlmdXZ2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxMjUwNzAsImV4cCI6MjA1NTcwMTA3MH0.xmcCqJUgvRtVsp6XmaH1YmUeerqZRZNQ_XmCnpOEFAo',
'origin: https://cybermonit.com',
'referer: https://cybermonit.com',
'user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/6.8.2 Chrome/122.0.6261.171 Safari/537.36',
'x-client-info: supabase-js-web/2.49.1',
);
$html = getContents($this->getURI(), $opts);
$data = json_decode($html, true);
array_walk_recursive($data, function(&$item, $key) {
$item = htmlspecialchars($item);
});
switch ($this->getKey("feed_type")) {
case 'Leaks':
foreach ($data as $obj) {
$this->items[] = [
'title' => sprintf("%s - %s", empty($obj['domain']) ? 'Unknown' : $obj['domain'], $obj['data_leaked']),
'timestamp' => $obj['breach_date'],
'uri' => "https://cybermonit.com/leaks",
'categories' => ['leaks'],
'content' => $obj['description'],
'uid' => sha1(sprintf("%s.%s.%s", $obj['domain'], $obj['breach_date'], self::SALT))
];
}
break;
case 'CVEs':
foreach ($data as $obj) {
$this->items[] = [
'title' => sprintf("%s (%s %s) - %s", $obj['cve_id'], $obj['score'], $obj['severity_en'], substr($obj['description'], 0, 150).'[...]'),
'timestamp' => $obj['publishedDate'],
'uri' => "https://cybermonit.com/cve",
"categories" => ['CVEs'],
'content' => $obj['description'],
'uid' => sha1(sprintf("%s.%s.%s", $obj['cve_id'], $obj['publishedDate'], self::SALT))
];
}
break;
case 'DDoS':
foreach ($data['targets'] as $obj) {
$this->items[] = [
'title' => sprintf("%s", $obj['host']),
'timestamp' => null,
'uri' => "https://cybermonit.com/ddos",
"categories" => ['DDoS'],
"content" => sprintf("IP: %s<br/>Type: %s<br>Method: %s<br>Port: %s<br>Use SSL: %s", $obj['ip'], $obj['type'], $obj['method'], $obj['port'], $obj['use_ssl'] == 1 ? "True" : "False"),
'uid' => sha1(sprintf("%s.%s.%s", $obj['host'], $obj['ip'], self::SALT))
];
}
break;
case 'Ransomware':
foreach ($data as $obj) {
$this->items[] = [
'title' => sprintf("%s attack on %s (%s)", $obj['group'], $obj['victim'], $obj['country']),
'timestamp' => $obj['attackdate'],
"categories" => ['Ransomware', $obj['group']],
'uri' => "https://cybermonit.com/ransomware",
"content" => "Ransomware attack ({$obj['group']}) on {$obj['victim']}, country: {$obj['country']}",
'uid' => sha1(sprintf("%s.%s.%s", $obj['victim'], $obj['attackdate'], self::SALT))
];
}
break;
case 'Releases':
foreach ($data as $obj) {
$this->items[] = [
'title' => sprintf("%s %s", $obj['nazwa_projektu'], $obj['wersja']),
'timestamp' => $obj['data_wydania'],
'categories' => ['Releases'],
'uri' => $obj['url_zdjecia'],
'content' => sprintf("%s updated to version %s. Project URL: %s, Release notes: %s", $obj['nazwa_projektu'], $obj['wersja'], $obj['url_projektu'], $obj['url_zdjecia']),
'uid' => sha1(sprintf("%s.%s.%s", $obj['nazwa_projektu'], $obj['data_wydania'], self::SALT))
];
}
break;
case 'EOL':
foreach ($data as $key => $v) {
$val = $v[0];
$this->items[] = [
'title' => sprintf("%s %s %s", $key, $val['cycle'], $val['lts'] == true ? 'LTS' : ''),
'timestamp'=> null,
'categories'=> ['EOL'],
'uri' => $this->getURI(),
'content' => sprintf("Product: %s<br>Version: %s<br>Release date: %s<br>End of life: %s", $key, $val['cycle'], $val['releaseDate'], $val['eol']),
'uid' => sha1(sprintf("%s.%s.%s", $key, $val['eol'], self::SALT))
];
}
break;
default:
break;
}
usort($this->items, function($a, $b) {
if ($a['timestamp'] == $b['timestamp']) {
return 0;
}
return ($a['timestamp'] > $b['timestamp']) ? -1 : 1;
});
}
}

View File

@ -48,16 +48,6 @@ https://www.dealabs.com/groupe/abonnements-internet?sortBy=lowest_price
Il faut alors saisir : Il faut alors saisir :
abonnements-internet', abonnements-internet',
], ],
'subgroups' => [
'name' => 'Catégorie',
'type' => 'text',
'exampleValue' => '1071',
'title' => 'Numéro du ou des catégories dans l\'URL : Il faut entrer le ou les numéros de catégories qui sont présent après "groups=" et avant tout éventuel "&"
Exemple : Si l\'URL du groupe affichées dans le navigateur est :
https://www.dealabs.com/groupe/telecommunications?groups=1071%2C1070&sortBy=new
Il faut alors saisir :
1071%2C1070',
],
'order' => [ 'order' => [
'name' => 'Trier par', 'name' => 'Trier par',
'type' => 'list', 'type' => 'list',
@ -98,7 +88,6 @@ Il faut alors saisir :
'uri-group' => 'groupe/', 'uri-group' => 'groupe/',
'uri-deal' => 'bons-plans/', 'uri-deal' => 'bons-plans/',
'uri-merchant' => 'search/bons-plans?merchant-id=', 'uri-merchant' => 'search/bons-plans?merchant-id=',
'image-host' => 'https://static-pepper.dealabs.com/',
'request-error' => 'Impossible de joindre Dealabs', 'request-error' => 'Impossible de joindre Dealabs',
'thread-error' => 'Impossible de déterminer l\'ID de la discussion. Vérifiez l\'URL que vous avez entré', 'thread-error' => 'Impossible de déterminer l\'ID de la discussion. Vérifiez l\'URL que vous avez entré',
'currency' => '€', 'currency' => '€',

View File

@ -1,50 +0,0 @@
<?php
class FeedProxyBridge extends FeedExpander
{
const MAINTAINER = 'void';
const NAME = 'FeedProxy';
const URI = '';
const DESCRIPTION = <<<'TEXT'
This bridge simply parse an external RSS<br>
TEXT;
const PARAMETERS = [
[
'feed_name' => [
'name' => 'Feed name',
'type' => 'text',
'exampleValue' => 'FeedProxy',
],
'feed_1' => [
'name' => 'Feed url',
'type' => 'text',
'required' => true,
'exampleValue' => 'https://lorem-rss.herokuapp.com/feed?unit=day'
],
'limit' => self::LIMIT,
]
];
/**
* TODO: Consider a strategy which produces a shorter feed url
*/
public function collectData()
{
$limit = (int)($this->getInput('limit') ?: 99);
$feed = $this->getInput('feed_1');
$this->collectExpandableDatas($feed, $limit);
// Sort by timestamp, uri, title in descending order
usort($this->items, function ($a, $b) {
$t1 = $a['timestamp'] ?? $a['uri'] ?? $a['title'];
$t2 = $b['timestamp'] ?? $b['uri'] ?? $b['title'];
return $t2 <=> $t1;
});
}
public function getName()
{
return $this->getInput('feed_name') ?: 'FeedProxy';
}
}

View File

@ -3,8 +3,7 @@
class FreeTelechargerBridge extends BridgeAbstract class FreeTelechargerBridge extends BridgeAbstract
{ {
const NAME = 'Free-Telecharger'; const NAME = 'Free-Telecharger';
const URI = 'https://www.free-telecharger.fun/'; const URI = 'https://www.free-telecharger.art/';
const ALTERNATEURI = 'https://www.free-telecharger.com/';
const DESCRIPTION = 'Suivi de série sur Free-Telecharger'; const DESCRIPTION = 'Suivi de série sur Free-Telecharger';
const MAINTAINER = 'sysadminstory'; const MAINTAINER = 'sysadminstory';
const PARAMETERS = [ const PARAMETERS = [
@ -13,19 +12,19 @@ class FreeTelechargerBridge extends BridgeAbstract
'name' => 'URL de la série', 'name' => 'URL de la série',
'type' => 'text', 'type' => 'text',
'required' => true, 'required' => true,
'title' => 'URL d\'une série sans le https://www.free-telecharger.fun/', 'title' => 'URL d\'une série sans le https://www.free-telecharger.art/',
'pattern' => 'series.*\.html', 'pattern' => 'series.*\.html',
'exampleValue' => 'series-vf-hd/151432-wolf-saison-1-complete-web-dl-720p.html' 'exampleValue' => 'series-vf-hd/151432-wolf-saison-1-complete-web-dl-720p.html'
], ],
] ]
]; ];
const CACHE_TIMEOUT = 3600; const CACHE_TIMEOUT = 3600;
private string $showTitle = ''; private string $showTitle;
private string $showTechDetails = ''; private string $showTechDetails;
public function collectData() public function collectData()
{ {
$html = getSimpleHTMLDOM(self::ALTERNATEURI . $this->getInput('url')); $html = getSimpleHTMLDOM(self::URI . $this->getInput('url'));
// Find all block content of the page // Find all block content of the page
$blocks = $html->find('div[class=block1]'); $blocks = $html->find('div[class=block1]');

View File

@ -192,18 +192,15 @@ class GithubIssueBridge extends BridgeAbstract
public function collectData() public function collectData()
{ {
$url = $this->getURI(); $html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($url);
switch ($this->queriedContext) { switch ($this->queriedContext) {
case static::BRIDGE_OPTIONS[1]: // Issue comments case static::BRIDGE_OPTIONS[1]: // Issue comments
$this->items = $this->extractIssueComments($html); $this->items = $this->extractIssueComments($html);
break; break;
case static::BRIDGE_OPTIONS[0]: // Project Issues case static::BRIDGE_OPTIONS[0]: // Project Issues
$issues = $html->find('.js-active-navigation-container .js-navigation-item'); foreach ($html->find('.js-active-navigation-container .js-navigation-item') as $issue) {
$issues = $html->find('.IssueRow-module__row--XmR1f'); $info = $issue->find('.opened-by', 0);
foreach ($issues as $issue) {
$info = $issue->find('.issue-item-module__authorCreatedLink--wFZvk', 0);
preg_match('/\/([0-9]+)$/', $issue->find('a', 0)->href, $match); preg_match('/\/([0-9]+)$/', $issue->find('a', 0)->href, $match);
$issueNbr = $match[1]; $issueNbr = $match[1];
@ -225,24 +222,24 @@ class GithubIssueBridge extends BridgeAbstract
$item['content'] = 'Can not extract comments from ' . $uri; $item['content'] = 'Can not extract comments from ' . $uri;
} }
$item['author'] = $issue->find('a', 1)->plaintext; $item['author'] = $info->find('a', 0)->plaintext;
$item['timestamp'] = strtotime( $item['timestamp'] = strtotime(
$issue->find('relative-time', 0)->getAttribute('datetime') $info->find('relative-time', 0)->getAttribute('datetime')
); );
$item['title'] = html_entity_decode( $item['title'] = html_entity_decode(
$issue->find('h3', 0)->plaintext, $issue->find('.js-navigation-open', 0)->plaintext,
ENT_QUOTES, ENT_QUOTES,
'UTF-8' 'UTF-8'
); );
//$comment_count = 0; $comment_count = 0;
//if ($span = $issue->find('a[aria-label*="comment"] span', 0)) { if ($span = $issue->find('a[aria-label*="comment"] span', 0)) {
// $comment_count = $span->plaintext; $comment_count = $span->plaintext;
//} }
//$item['content'] .= "\n" . 'Comments: ' . $comment_count; $item['content'] .= "\n" . 'Comments: ' . $comment_count;
$item['uri'] = self::URI $item['uri'] = self::URI
. trim($issue->find('a', 0)->getAttribute('href'), '/'); . trim($issue->find('.js-navigation-open', 0)->getAttribute('href'), '/');
$this->items[] = $item; $this->items[] = $item;
} }
break; break;

View File

@ -1,6 +1,6 @@
<?php <?php
class GovTrackBridge extends FeedExpander class GovTrackBridge extends BridgeAbstract
{ {
const NAME = 'GovTrack'; const NAME = 'GovTrack';
const MAINTAINER = 'phantop'; const MAINTAINER = 'phantop';
@ -18,50 +18,64 @@ class GovTrackBridge extends FeedExpander
'Major Legislative Activity' => 'major-bill-activity', 'Major Legislative Activity' => 'major-bill-activity',
'New Bills and Resolutions' => 'introduced-bills', 'New Bills and Resolutions' => 'introduced-bills',
'New Laws' => 'enacted-bills', 'New Laws' => 'enacted-bills',
'News from Us' => 'posts' 'Posts from Us' => 'posts'
] ]
], ],
'limit' => self::LIMIT 'limit' => self::LIMIT
]]; ]];
public function collectData() public function collectData()
{ {
$limit = $this->getInput('limit') ?? 15; $html = getSimpleHTMLDOMCached($this->getURI());
if ($this->getInput('feed') == 'posts') { if ($this->getInput('feed') != 'posts') {
$this->collectExpandableDatas($this->getURI() . '.rss', $limit); $this->collectEvent($html);
} else { return;
$this->collectEvent($this->getURI(), $limit); }
$html = defaultLinkTo($html, parent::getURI());
$limit = $this->getInput('limit') ?? 10;
foreach ($html->find('section') as $element) {
if (--$limit == 0) {
break;
}
$info = explode(' ', $element->find('p', 0)->innertext);
$item = [
'categories' => [implode(' ', array_slice($info, 4))],
'timestamp' => strtotime(implode(' ', array_slice($info, 0, 3))),
'title' => $element->find('a', 0)->innertext,
'uri' => $element->find('a', 0)->href,
];
$html = getSimpleHTMLDOMCached($item['uri']);
$html = defaultLinkTo($html, parent::getURI());
$content = $html->find('#content .col-md', 1);
$info = explode(' by ', $content->find('p', 0)->plaintext);
$content->removeChild($content->firstChild());
$item['author'] = implode(' ', array_slice($info, 1));
$item['content'] = $content->innertext;
$this->items[] = $item;
} }
} }
protected function parseItem(array $item) private function collectEvent($html)
{ {
$html = getSimpleHTMLDOMCached($item['uri']); $opt = [];
$html = defaultLinkTo($html, parent::getURI()); preg_match('/"csrfmiddlewaretoken" value="(.*)"/', $html, $opt);
$item['categories'] = [$html->find('.breadcrumb-item', 1)->plaintext];
$content = $html->find('#content .col-md', 1);
$item['author'] = explode(' by ', $content->firstChild()->plaintext)[1];
$content->removeChild($content->firstChild());
$item['content'] = $content->innertext;
return $item;
}
private function collectEvent($uri, $limit)
{
$html = getSimpleHTMLDOMCached($uri);
preg_match('/"csrfmiddlewaretoken" value="(.*)"/', $html, $preg);
$header = [ $header = [
"cookie: csrftoken=$preg[1]", "cookie: csrftoken=$opt[1]",
"x-csrftoken: $preg[1]", "x-csrftoken: $opt[1]",
'referer: ' . parent::getURI(), 'referer: ' . parent::getURI(),
]; ];
preg_match('/var selected_feed = "(.*)";/', $html, $preg); preg_match('/var selected_feed = "(.*)";/', $html, $opt);
$opt = [ CURLOPT_POSTFIELDS => [ $post = [
'count' => $limit, 'count' => $this->getInput('limit') ?? 20,
'feed' => $preg[1] 'feed' => $opt[1]
]]; ];
$opt = [ CURLOPT_POSTFIELDS => $post ];
$html = getContents(parent::getURI() . 'events/_load_events', $header, $opt); $html = getContents(parent::getURI() . 'events/_load_events', $header, $opt);
$html = defaultLinkTo(str_get_html($html), parent::getURI()); $html = defaultLinkTo(str_get_html($html), parent::getURI());
@ -69,10 +83,10 @@ class GovTrackBridge extends FeedExpander
foreach ($html->find('.tracked_event') as $event) { foreach ($html->find('.tracked_event') as $event) {
$bill = $event->find('.event_title a, .event_body a', 0); $bill = $event->find('.event_title a, .event_body a', 0);
$date = explode(' ', $event->find('.event_date', 0)->plaintext); $date = explode(' ', $event->find('.event_date', 0)->plaintext);
preg_match('/Sponsor:(.*)\n/', $event->plaintext, $preg); preg_match('/Sponsor:(.*)\n/', $event->plaintext, $opt);
$item = [ $item = [
'author' => $preg[1] ?? '', 'author' => $opt[1] ?? '',
'content' => $event->find('td', 1)->innertext, 'content' => $event->find('td', 1)->innertext,
'enclosures' => [$event->find('img', 0)->src], 'enclosures' => [$event->find('img', 0)->src],
'timestamp' => strtotime(implode(' ', array_slice($date, 2))), 'timestamp' => strtotime(implode(' ', array_slice($date, 2))),
@ -101,10 +115,10 @@ class GovTrackBridge extends FeedExpander
public function getURI() public function getURI()
{ {
if ($this->getInput('feed') == 'posts') { if ($this->getInput('feed') != 'posts') {
$url = parent::getURI() . $this->getInput('feed');
} else {
$url = parent::getURI() . 'events/' . $this->getInput('feed'); $url = parent::getURI() . 'events/' . $this->getInput('feed');
} else {
$url = parent::getURI() . $this->getInput('feed');
} }
return $url; return $url;
} }

View File

@ -47,16 +47,6 @@ Example: If the URL of the group displayed in the browser is :
https://www.hotukdeals.com/tag/broadband?sortBy=temp https://www.hotukdeals.com/tag/broadband?sortBy=temp
Then enter : Then enter :
broadband', broadband',
],
'subgroups' => [
'name' => 'category',
'type' => 'text',
'exampleValue' => '343563',
'title' => 'Category number in the URL : The category number that must be entered is present after "groups=" and before any "&".
Example: If the URL of the group displayed in the browser is :
https://www.hotukdeals.com/tag/broadband?groups=343563&sortBy=new
Then enter :
343563',
], ],
'order' => [ 'order' => [
'name' => 'Order by', 'name' => 'Order by',
@ -96,7 +86,6 @@ Then enter :
'uri-group' => 'tag/', 'uri-group' => 'tag/',
'uri-deal' => 'deals/', 'uri-deal' => 'deals/',
'uri-merchant' => 'search/deals?merchant-id=', 'uri-merchant' => 'search/deals?merchant-id=',
'image-host' => 'https://images.hotukdeals.com/',
'request-error' => 'Could not request HotUKDeals', 'request-error' => 'Could not request HotUKDeals',
'thread-error' => 'Unable to determine the thread ID. Check the URL you entered', 'thread-error' => 'Unable to determine the thread ID. Check the URL you entered',
'currency' => '£', 'currency' => '£',

View File

@ -86,11 +86,6 @@ class InstagramBridge extends BridgeAbstract
$headers = []; $headers = [];
$sessionId = $this->getOption('session_id'); $sessionId = $this->getOption('session_id');
$dsUserId = $this->getOption('ds_user_id'); $dsUserId = $this->getOption('ds_user_id');
$headers[] = 'x-ig-app-id: 936619743392459';
$headers[] = 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36';
$headers[] = 'Accept-Language: en-US,en;q=0.9,ru;q=0.8';
$headers[] = 'Accept-Encoding: gzip, deflate, br';
$headers[] = 'Accept: */*';
if ($sessionId and $dsUserId) { if ($sessionId and $dsUserId) {
$headers[] = 'cookie: sessionid=' . $sessionId . '; ds_user_id=' . $dsUserId; $headers[] = 'cookie: sessionid=' . $sessionId . '; ds_user_id=' . $dsUserId;
} }
@ -130,10 +125,8 @@ class InstagramBridge extends BridgeAbstract
return; return;
} }
if (!is_null($this->getInput('u')) && !$this->fallbackMode) { if (!is_null($this->getInput('u'))) {
$userMedia = $data->data->user->edge_owner_to_timeline_media->edges; $userMedia = $data->data->user->edge_owner_to_timeline_media->edges;
} elseif (!is_null($this->getInput('u')) && $this->fallbackMode) {
$userMedia = $data->context->graphql_media;
} elseif (!is_null($this->getInput('h'))) { } elseif (!is_null($this->getInput('h'))) {
$userMedia = $data->data->hashtag->edge_hashtag_to_media->edges; $userMedia = $data->data->hashtag->edge_hashtag_to_media->edges;
} elseif (!is_null($this->getInput('l'))) { } elseif (!is_null($this->getInput('l'))) {
@ -141,12 +134,7 @@ class InstagramBridge extends BridgeAbstract
} }
foreach ($userMedia as $media) { foreach ($userMedia as $media) {
// The media is not in the same element if in fallback mode than not $media = $media->node;
if (!$this->fallbackMode) {
$media = $media->node;
} else {
$media = $media->shortcode_media;
}
switch ($this->getInput('media_type')) { switch ($this->getInput('media_type')) {
case 'all': case 'all':
@ -279,39 +267,14 @@ class InstagramBridge extends BridgeAbstract
protected function getInstagramJSON($uri) protected function getInstagramJSON($uri)
{ {
// Sets fallbackMode to false
$this->fallbackMode = false;
if (!is_null($this->getInput('u'))) { if (!is_null($this->getInput('u'))) {
try { $userId = $this->getInstagramUserId($this->getInput('u'));
$userId = $this->getInstagramUserId($this->getInput('u')); $data = $this->getContents(self::URI .
$data = $this->getContents(self::URI .
'graphql/query/?query_hash=' . 'graphql/query/?query_hash=' .
self::USER_QUERY_HASH . self::USER_QUERY_HASH .
'&variables={"id"%3A"' . '&variables={"id"%3A"' .
$userId . $userId .
'"%2C"first"%3A10}'); '"%2C"first"%3A10}');
} catch (HttpException $e) {
// If loading the data directly failed, we fall back to the "/embed" data loading
// We are in the fallback mode : set a booolean to handle this specific case while collecting the content
$this->fallbackMode = true;
// Get the HTML code of the profile embed page, and extract the JSON of it
$username = $this->getInput('u');
// Load the content using the integrated function to use helping headers
$htmlString = $this->getContents(self::URI . $username . '/embed/');
// Load the String as an SimpleHTMLDom Object
$html = new simple_html_dom();
$html->load($htmlString);
// Find the <script> tag containing the JSON content
$jsCode = $html->find('body', 0)->find('script', 3)->innertext;
// Extract the content needed by our bridge of the whole Javascript content
$regex = '#"contextJSON":"(.*)"}\]\],\["NavigationMetrics"#m';
preg_match($regex, $jsCode, $matches);
$jsVariable = $matches[1];
$data = stripcslashes($jsVariable);
// stripcslashes remove Javascript unicode escaping : add it back to the string so json_decode can handle it
$data = preg_replace('/(?<!\\\\)u[0-9A-Fa-f]{4}/', '\\\\$0', $data);
}
return json_decode($data); return json_decode($data);
} elseif (!is_null($this->getInput('h'))) { } elseif (!is_null($this->getInput('h'))) {
$data = $this->getContents(self::URI . $data = $this->getContents(self::URI .

View File

@ -1,118 +0,0 @@
<?php
class LeagueOfLegendsNewsBridge extends BridgeAbstract
{
const NAME = 'League of Legends News';
const URI = 'https://www.leagueoflegends.com';
const DESCRIPTION = 'Official League of Legends news.';
const MAINTAINER = 'KappaPrajd';
const PARAMETERS = [
[
'language' => [
'name' => 'Language',
'type' => 'list',
'defaultValue' => 'en-us',
'values' => [
'English (NA)' => 'en-us',
'English (EUW)' => 'en-gb',
'Deutsch' => 'de-de',
'Español (EUW)' => 'es-es',
'Français' => 'fr-fr',
'Italiano' => 'it-it',
'Polski' => 'pl-pl',
'Ελληνικά' => 'el-gr',
'Română' => 'ro-ro',
'Magyar' => 'hu-hu',
'Čeština' => 'cs-cz',
'Español (LATAM)' => 'es-mx',
'Português' => 'pt-br',
'日本語' => 'ja-jp',
'Русский' => 'ru-ru',
'Türkçe' => 'tr-tr',
'English (OCE)' => 'en-au',
'한국어' => 'ko-kr',
'English (SG)' => 'en-sg',
'English (PH)' => 'en-ph',
'Tiếng Việt' => 'vi-vn',
'ภาษาไทย' => 'th-th',
'繁體中文' => 'zh-tw',
'العربية' => 'ar-ae'
]
],
'category' => [
'name' => 'Category',
'type' => 'list',
'defaultValue' => 'all',
'values' => [
'All' => 'all',
'Game updates' => 'game-updates',
'Esports' => 'esports',
'Dev' => 'dev',
'Lore' => 'lore',
'Media' => 'media',
'Merch' => 'merch',
'Community' => 'community',
'Riot Games' => 'riot-games'
]
],
'onlyPatchNotes' => [
'name' => 'Only patch notes',
'type' => 'checkbox',
'defaultValue' => false,
],
],
];
public function collectData()
{
$siteUrl = $this->getSiteUrl();
$html = getSimpleHTMLDOM($siteUrl);
$articles = $html->find('a[data-testid=articlefeaturedcard-component]');
foreach ($articles as $article) {
$title = $article->find('div[data-testid=card-title]', 0)->plaintext;
$content = $article->find('div[data-testid=card-description] div div div', 0)->plaintext;
$timestamp = $article->find('div[data-testid=card-date] time', 0)->getAttribute('datetime');
$href = $article->getAttribute('href');
$item = [
'title' => $title,
'content' => $content,
'timestamp' => $timestamp,
'uri' => $this->getArticleUri($href),
];
$this->items[] = $item;
}
}
private function getSiteUrl()
{
$lang = $this->getInput('language');
$category = $this->getInput('category');
$onlyPatchNotes = $this->getInput('onlyPatchNotes');
$url = self::URI . '/' . $lang . '/news';
if ($onlyPatchNotes) {
return $url . '/tags/patch-notes';
} else if ($category === 'all') {
return $url;
}
return $url . '/' . $category;
}
private function getArticleUri($href)
{
$isInternalLink = str_starts_with($href, '/');
if ($isInternalLink) {
return self::URI . $href;
}
return $href;
}
}

View File

@ -78,8 +78,8 @@ class LfcPlBridge extends BridgeAbstract
foreach ($commentsDom as $comment) { foreach ($commentsDom as $comment) {
$header = $comment->find('.header', 0)->plaintext; $header = $comment->find('.header', 0)->plaintext;
$commentContent = $comment->find('.content', 0)->plaintext; $content = $comment->find('.content', 0)->plaintext;
$comments .= $header . '<br />' . $commentContent . '<br /><br />'; $comments .= $header . '<br />' . $content . '<br /><br />';
} }
} }
} }

View File

@ -48,16 +48,6 @@ https://www.mydealz.de/gruppe/dsl?sortBy=temp
Dann geben Sie ein: Dann geben Sie ein:
dsl', dsl',
], ],
'subgroups' => [
'name' => 'Kategorie',
'type' => 'text',
'exampleValue' => '293',
'title' => 'Nummer des Kategorie in der URL: Der einzugebende Kategorienummer steht nach "groups=" und vor einem "&".
Beispiel: Wenn die URL der Gruppe, die im Browser angezeigt wird, :
https://www.mydealz.de/gruppe/telefon-internet?groups=153%2C154&sortBy=new&time_frame=0
Dann geben Sie ein:
153%2C154',
],
'order' => [ 'order' => [
'name' => 'sortieren nach', 'name' => 'sortieren nach',
'type' => 'list', 'type' => 'list',
@ -94,7 +84,6 @@ Dann geben Sie ein:
'uri-group' => 'gruppe/', 'uri-group' => 'gruppe/',
'uri-deal' => 'deals/', 'uri-deal' => 'deals/',
'uri-merchant' => 'search/gutscheine?merchant-id=', 'uri-merchant' => 'search/gutscheine?merchant-id=',
'image-host' => 'https://static.mydealz.de/',
'request-error' => 'Could not request mydeals', 'request-error' => 'Could not request mydeals',
'thread-error' => 'Die ID der Diskussion kann nicht ermittelt werden. Überprüfen Sie die eingegebene URL', 'thread-error' => 'Die ID der Diskussion kann nicht ermittelt werden. Überprüfen Sie die eingegebene URL',
'currency' => '€', 'currency' => '€',

View File

@ -6,7 +6,7 @@ class NurembergerNachrichtenBridge extends BridgeAbstract
const NAME = 'Nürnberger Nachrichten'; const NAME = 'Nürnberger Nachrichten';
const CACHE_TIMEOUT = 3600; const CACHE_TIMEOUT = 3600;
const URI = 'https://www.nn.de'; const URI = 'https://www.nn.de';
const DESCRIPTION = 'Bridge for NurembergerNachrichten news site nn.de'; const DESCRIPTION = 'Bridge for Bavarian regional news site nordbayern.de';
const PARAMETERS = [ [ const PARAMETERS = [ [
'region' => [ 'region' => [
'name' => 'region', 'name' => 'region',
@ -66,7 +66,7 @@ class NurembergerNachrichtenBridge extends BridgeAbstract
// exclude nn+ articles if desired // exclude nn+ articles if desired
if ( if (
$this->getInput('hideNNPlus') && $this->getInput('hideNNPlus') &&
$articleContent->find('div[class=paywall]') str_contains($articleContent->find('article[id=article]', 0)->find('header', 0), 'icon-nnplus')
) { ) {
continue; continue;
} }

View File

@ -1,54 +0,0 @@
<?php
class OSVBridge extends BridgeAbstract
{
const NAME = 'OSV';
const DESCRIPTION = 'Parse osv.dev vulns';
const MAINTAINER = 'void';
const URI = "https://osv.dev/list";
const MAIN_DOMAIN = "https://osv.dev";
const PARAMETERS = [[
]];
protected function parseItems($html) {
foreach ($html->find('.vuln-table-row') as $element) {
$item = [];
$link = $element->find('.vuln-table-cell > a', 0);
if (empty($link)) continue;
$time = $element->find('span.vuln-table-cell',3)->find('relative-time',0);
$summary = $element->find('.vuln-summary',0);
$tags = $element->find('ul.tags > li',0);
$item['uri'] = self::MAIN_DOMAIN . $link->href;
$item['title'] = $link->innertext;
$item['title'] .= sprintf(" - %s", trim($summary->innertext));
$item['timestamp'] = $time->datetime;
$packages = "";
foreach ($element->find('ul.packages > li',0) as $pack) {
$packages .= !empty($pack->innertext) ? "<li>$pack->innertext</li>" : "";
}
$tagsout = "";
foreach ($element->find('ul.tags > li > span') as $tag) {
$tagsout .= !empty($tag->innertext) ? "<li>{$tag->innertext}</li>" : "";
}
$item['content'] = "{$link->innertext} - {$summary->innertext}<br/>Published: {$time->innertext}<br/>Packages: <ul>{$packages}</ul><br/>Tags: <ul>{$tagsout}</ul>";
$item['uid'] = $link->innertext;
$this->items[] = $item;
}
}
public function collectData()
{
$html = getSimpleHTMLDOM($this->getURI());
$this->parseItems($html);
usort($this->items, function($a, $b) {
if ($a['timestamp'] == $b['timestamp']) {
return 0;
}
return ($a['timestamp'] > $b['timestamp']) ? -1 : 1;
});
}
}

View File

@ -62,7 +62,7 @@ class PepperBridgeAbstract extends BridgeAbstract
foreach ($list as $deal) { foreach ($list as $deal) {
// Get the JSON Data stored as vue // Get the JSON Data stored as vue
$jsonDealData = $this->getDealJsonData($deal); $jsonDealData = $this->getDealJsonData($deal);
$dealMeta = Json::decode($deal->find('div[class=js-vue2]', 1)->getAttribute('data-vue2')); $dealMeta = Json::decode($deal->find('div[class=threadGrid-headerMeta]', 0)->find('div[class=js-vue2]', 1)->getAttribute('data-vue2'));
$item = []; $item = [];
$item['uri'] = $this->getDealURI($jsonDealData); $item['uri'] = $this->getDealURI($jsonDealData);
@ -80,7 +80,7 @@ class PepperBridgeAbstract extends BridgeAbstract
. $this->getShipsFrom($dealMeta) . $this->getShipsFrom($dealMeta)
. $this->getShippingCost($jsonDealData) . $this->getShippingCost($jsonDealData)
. $this->getSource($jsonDealData) . $this->getSource($jsonDealData)
. $this->getDealLocation($jsonDealData) . $this->getDealLocation($dealMeta)
. $deal->find('div[class*=' . $selectorDescription . ']', 0)->innertext . $deal->find('div[class*=' . $selectorDescription . ']', 0)->innertext
. '</td><td>' . '</td><td>'
. $this->getTemperature($jsonDealData) . $this->getTemperature($jsonDealData)
@ -402,9 +402,14 @@ HEREDOC;
* Get the Deal location if it exists * Get the Deal location if it exists
* @return string String of the deal location * @return string String of the deal location
*/ */
private function getDealLocation($jsonDealData) private function getDealLocation($dealMeta)
{ {
if ($jsonDealData['props']['thread']['isLocal']) { $ribbons = $dealMeta['props']['metaRibbons'];
$isLocal = false;
foreach ($ribbons as $ribbon) {
$isLocal |= ($ribbon['type'] == 'local');
}
if ($isLocal) {
$content = '<div>' . $this->i8n('deal-type') . ' : ' . $this->i8n('localdeal') . '</div>'; $content = '<div>' . $this->i8n('deal-type') . ' : ' . $this->i8n('localdeal') . '</div>';
} else { } else {
$content = ''; $content = '';
@ -419,11 +424,8 @@ HEREDOC;
private function getImage($deal) private function getImage($deal)
{ {
// Get thread Image JSON content // Get thread Image JSON content
$content = Json::decode($deal->find('div[class=js-vue2]', 0)->getAttribute('data-vue2')); $content = Json::decode($deal->find('div[class*=threadGrid-image]', 0)->find('div[class=js-vue2]', 0)->getAttribute('data-vue2'));
//return '<img src="' . $content['props']['threadImageUrl'] . '"/>'; return '<img src="' . $content['props']['threadImageUrl'] . '"/>';
return '<img src="' . $this->i8n('image-host') . $content['props']['thread']['mainImage']['path'] . '/'
. $content['props']['thread']['mainImage']['name'] . '/re/202x202/qt/70/'
. $content['props']['thread']['mainImage']['uid'] . '"/>';
} }
/** /**
@ -432,7 +434,7 @@ HEREDOC;
*/ */
private function getShipsFrom($dealMeta) private function getShipsFrom($dealMeta)
{ {
$metas = $dealMeta['props']['metaRibbons'] ?? []; $metas = $dealMeta['props']['metaRibbons'];
$shipsFrom = null; $shipsFrom = null;
foreach ($metas as $meta) { foreach ($metas as $meta) {
if ($meta['type'] == 'dispatched-from') { if ($meta['type'] == 'dispatched-from') {
@ -522,7 +524,6 @@ HEREDOC;
{ {
$group = $this->getInput('group'); $group = $this->getInput('group');
$order = $this->getInput('order'); $order = $this->getInput('order');
$subgroups = $this->getInput('subgroups');
// This permit to keep the existing Feed to work // This permit to keep the existing Feed to work
if ($order == $this->i8n('context-hot')) { if ($order == $this->i8n('context-hot')) {
@ -532,7 +533,7 @@ HEREDOC;
} }
$url = $this->i8n('bridge-uri') $url = $this->i8n('bridge-uri')
. $this->i8n('uri-group') . $group . '?sortBy=' . $sortBy . '&groups=' . $subgroups; . $this->i8n('uri-group') . $group . '?sortBy=' . $sortBy;
return $url; return $url;
} }

View File

@ -40,7 +40,7 @@ class RadioMelodieBridge extends BridgeAbstract
$picture = []; $picture = [];
// Get the Main picture URL // Get the Main picture URL
$picture[] = $article->find('figure[class*=photoviewer]', 0)->find('img', 0)->src; $picture[] = self::URI . $article->find('figure[class*=photoviewer]', 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
@ -123,7 +123,7 @@ class RadioMelodieBridge extends BridgeAbstract
preg_match('/wavesurfer[0-9]+.load\(\'(.*)\'\)/m', $js->innertext, $urls); preg_match('/wavesurfer[0-9]+.load\(\'(.*)\'\)/m', $js->innertext, $urls);
// Create the plain HTML <audio> content to play this audio file // Create the plain HTML <audio> content to play this audio file
$content = '<audio style="width: 100%" src="' . self::URI . $urls[1] . '" controls ></audio>'; $content = '<audio style="width: 100%" src="' . $urls[1] . '" controls ></audio>';
// Replace the <script> tag by the <audio> tag // Replace the <script> tag by the <audio> tag
$js->outertext = $content; $js->outertext = $content;

View File

@ -35,7 +35,7 @@ class ReutersBridge extends BridgeAbstract
'title' => 'Feeds from Reuters U.S/International edition', 'title' => 'Feeds from Reuters U.S/International edition',
'values' => [ 'values' => [
'Top News' => 'home/topnews', 'Top News' => 'home/topnews',
'Fact Check' => '/fact-check', 'Fact Check' => 'chan:abtpk0vm',
'Entertainment' => 'chan:8ym8q8dl', 'Entertainment' => 'chan:8ym8q8dl',
'Politics' => 'politics', 'Politics' => 'politics',
'Wire' => 'wire', 'Wire' => 'wire',
@ -137,6 +137,7 @@ class ReutersBridge extends BridgeAbstract
const OLD_WIRE_SECTION = [ const OLD_WIRE_SECTION = [
'home/topnews', 'home/topnews',
'chan:abtpk0vm',
'chan:8ym8q8dl', 'chan:8ym8q8dl',
'politics', 'politics',
'wire' 'wire'

View File

@ -1,100 +0,0 @@
<?php
class ShadertoyBridge extends BridgeAbstract
{
const NAME = 'Shadertoy';
const URI = 'https://www.shadertoy.com';
const DESCRIPTION = 'Latest submissions on Shadertoy';
const MAINTAINER = 'thefranke';
const CACHE_TIMEOUT = 3600; // 1h
const PARAMETERS = [
[
'category' => [
'name' => 'category',
'type' => 'list',
'exampleValue' => 'Popular',
'title' => 'Select a category',
'values' => [
'Shaders of the Week' => 'sotw',
'Popular' => 'popular',
'Newest' => 'newest',
'Hot' => 'hot',
]
]
]
];
public function postprocessDescription($content)
{
// replace [url] tags
$pattern = '/\[\/?url.*?\]/';
$replace = '';
$content = preg_replace($pattern, $replace, $content);
// find URLs and turn then into hyperlinks
$pattern = '/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/';
$replace = '<a href="$0">$0</a>';
$content = preg_replace($pattern, $replace, $content);
return $content;
}
public function collectData()
{
$category = $this->getInput('category');
$json = null;
if ($category == 'sotw') {
$url = static::URI . '/playlist/week';
$contents = getContents($url);
$shaderids = extractFromDelimiters($contents, 'var gShaderIDs = ', ';');
$shaderids = str_replace('\'', '"', $shaderids);
$url = static::URI . '/shadertoy';
$data = 's=' . rawurlencode('{ "shaders": ' . $shaderids . ' }') . '&nt=0&nl=0&np=0';
$header = [
'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:135.0) Gecko/20100101 Firefox/135.0',
'Content-Type: application/x-www-form-urlencoded',
'Accept: */*',
'Origin: https://www.shadertoy.com',
'Referer: https://www.shadertoy.com/playlist/week',
];
$opts = [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_RETURNTRANSFER => true
];
$json = getContents($url, $header, $opts);
} else {
$url = static::URI . '/results?sort=' . $category;
$contents = getContents($url);
$json = extractFromDelimiters($contents, 'var gShaders=', 'var gUseScreenshots');
$json = substr(trim($json), 0, -1);
}
$json = Json::decode($json);
if (!$json) {
throw new Exception(sprintf('Unable to find css selector on `%s`', static::URI));
}
foreach ($json as $article) {
$id = $article['info']['id'];
$title = $article['info']['name'];
$author = $article['info']['username'];
$uri = static::URI . '/view/' . $id;
$content = '<p><img src="' . static::URI . '/media/shaders/' . $id . '.jpg"></p><p>' . $this->postprocessDescription($article['info']['description']) . '</p>';
$timestamp = $article['info']['date'];
$this->items[] = [
'title' => $title,
'author' => $author,
'uri' => $uri,
'content' => $content,
'timestamp' => $timestamp,
];
}
}
}

View File

@ -57,9 +57,6 @@ class TldrTechBridge extends BridgeAbstract
continue; continue;
} }
$itemUrl = Url::fromString(self::URI . ltrim($child->href, '/')); $itemUrl = Url::fromString(self::URI . ltrim($child->href, '/'));
if ($itemUrl == $locationUrl) {
continue;
}
$this->extractItem($itemUrl); $this->extractItem($itemUrl);
if (count($this->items) >= $limit) { if (count($this->items) >= $limit) {
break; break;
@ -128,11 +125,6 @@ class TldrTechBridge extends BridgeAbstract
} }
} }
} }
foreach ($content->find('section') as $section) {
if (count($section->children()) == 0) {
$content->removeChild($section);
}
}
$title = $content->find('h2', 0); $title = $content->find('h2', 0);
return [$content->innertext, $title->plaintext]; return [$content->innertext, $title->plaintext];
} }

View File

@ -1,28 +0,0 @@
<?php
class TomsToucheBridge extends BridgeAbstract
{
const NAME = 'Toms Touché';
const URI = 'https://taz.de/#!tom=tomdestages';
const DESCRIPTION = 'Your daily dose of Toms Touche.';
const MAINTAINER = 'latz';
const CACHE_TIMEOUT = 3600; // 1h
public function collectData()
{
$url = 'https://taz.de/';
$html = getSimpleHTMLDOM($url); // Docs: https://simplehtmldom.sourceforge.io/docs/1.9/index.html
$date = $html->find('p[x-ref]');
$date = trim($date[0]->innertext);
[$day, $month, $year] = explode('.', $date);
$image = $html->find('img[alt="tom des tages"]');
$item = [];
$item['title'] = "Toms Touché - $date";
$item['uri'] = 'https://taz.de/#!tom=tomdestages';
$item['timestamp'] = mktime(0, 0, 0, $month, $day, $year);
$item['content'] = $image[0] . '</img>'; // This isn't good HTML style, but at least syntactically correct
$item['uid'] = $image[0]->getAttribute('src');
$this->items[] = $item;
}
}

View File

@ -7,8 +7,8 @@ class VkBridge extends BridgeAbstract
// const MAINTAINER = 'ahiles3005'; // const MAINTAINER = 'ahiles3005';
const NAME = 'VK.com'; const NAME = 'VK.com';
const URI = 'https://vk.com/'; const URI = 'https://vk.com/';
const CACHE_TIMEOUT = 3600; // 1h const CACHE_TIMEOUT = 300; // 5min
const DESCRIPTION = 'Does not work anymore'; const DESCRIPTION = 'Working with open pages';
const PARAMETERS = [ const PARAMETERS = [
[ [
'u' => [ 'u' => [
@ -65,7 +65,6 @@ class VkBridge extends BridgeAbstract
public function collectData() public function collectData()
{ {
return;
$text_html = $this->getContents(); $text_html = $this->getContents();
$text_html = iconv('windows-1251', 'utf-8//ignore', $text_html); $text_html = iconv('windows-1251', 'utf-8//ignore', $text_html);
@ -392,13 +391,10 @@ class VkBridge extends BridgeAbstract
$item['categories'] = $hashtags; $item['categories'] = $hashtags;
// get post link // get post link
$var = $post->find('a.PostHeaderSubtitle__link', 0); $post_link = $post->find('a.PostHeaderSubtitle__link', 0)->getAttribute('href');
if ($var) { preg_match('/wall-?\d+_(\d+)/', $post_link, $preg_match_result);
$post_link = $var->getAttribute('href'); $item['post_id'] = intval($preg_match_result[1]);
preg_match('/wall-?\d+_(\d+)/', $post_link, $preg_match_result); $item['uri'] = $post_link;
$item['post_id'] = intval($preg_match_result[1]);
$item['uri'] = $post_link;
}
$item['timestamp'] = $this->getTime($post); $item['timestamp'] = $this->getTime($post);
$item['title'] = $this->getTitle($item['content']); $item['title'] = $this->getTitle($item['content']);
$item['author'] = $post_author; $item['author'] = $post_author;
@ -406,7 +402,7 @@ class VkBridge extends BridgeAbstract
// do not append it now // do not append it now
$pinned_post_item = $item; $pinned_post_item = $item;
} else { } else {
$last_post_id = $item['post_id'] ?? null; $last_post_id = $item['post_id'];
$this->items[] = $item; $this->items[] = $item;
} }
} }
@ -478,10 +474,7 @@ class VkBridge extends BridgeAbstract
if ($accurateDateElement) { if ($accurateDateElement) {
return $accurateDateElement->getAttribute('time'); return $accurateDateElement->getAttribute('time');
} else { } else {
$strdate = $post->find('time.PostHeaderSubtitle__item', 0)->plaintext ?? null; $strdate = $post->find('time.PostHeaderSubtitle__item', 0)->plaintext;
if (!$strdate) {
return 0;
}
$strdate = preg_replace('/[\x00-\x1F\x7F-\xFF]/', ' ', $strdate); $strdate = preg_replace('/[\x00-\x1F\x7F-\xFF]/', ' ', $strdate);
$date = date_parse($strdate); $date = date_parse($strdate);

View File

@ -1,86 +0,0 @@
<?php
class YouTubeFeedExpanderBridge extends FeedExpander
{
const NAME = 'YouTube Feed Expander';
const MAINTAINER = 'phantop';
const URI = 'https://www.youtube.com/';
const DESCRIPTION = 'Returns the latest videos from a YouTube channel';
const PARAMETERS = [[
'channel' => [
'name' => 'Channel ID',
'required' => true,
// Example: vinesauce
'exampleValue' => 'UCzORJV8l3FWY4cFO8ot-F2w',
],
'embed' => [
'name' => 'Add embed to entry',
'type' => 'checkbox',
'required' => false,
'title' => 'Add embed to entry',
'defaultValue' => 'checked',
],
'embedurl' => [
'name' => 'Use embed page as entry url',
'type' => 'checkbox',
'required' => false,
'title' => 'Use embed page as entry url',
],
'nocookie' => [
'name' => 'Use nocookie embed page',
'type' => 'checkbox',
'required' => false,
'title' => 'Use nocookie embed page'
],
]];
public function getIcon()
{
if ($this->getInput('channel') != null) {
$html = getSimpleHTMLDOMCached($this->getURI());
$scriptRegex = '/var ytInitialData = (.*?);<\/script>/';
$result = preg_match($scriptRegex, $html, $matches);
if (isset($matches[1])) {
$json = json_decode($matches[1]);
return $json->metadata->channelMetadataRenderer->avatar->thumbnails[0]->url;
}
}
return parent::getIcon();
}
public function collectData()
{
$url = 'https://www.youtube.com/feeds/videos.xml?channel_id=' . $this->getInput('channel');
$this->collectExpandableDatas($url);
}
protected function parseItem(array $item)
{
$id = $item['yt']['videoId'];
$item['comments'] = $item['uri'] . '#comments';
$item['uid'] = $item['id'];
$thumbnail = sprintf('https://img.youtube.com/vi/%s/maxresdefault.jpg', $id);
$item['enclosures'] = [$thumbnail];
$item['content'] = $item['media']['group']['description'];
$item['content'] = str_replace("\n", '<br>', $item['content']);
unset($item['media']);
$embedURI = self::URI;
if ($this->getInput('nocookie')) {
$embedURI = 'https://www.youtube-nocookie.com/';
}
$embed = $embedURI . 'embed/' . $id;
if ($this->getInput('embed')) {
$iframe_fmt = '<iframe width="448" height="350" src="%s" title="%s" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>'; //phpcs:ignore
$iframe = sprintf($iframe_fmt, $embed, $item['title']) . '<br>';
$item['content'] = $iframe . $item['content'];
}
if ($this->getInput('embedurl')) {
$item['uri'] = $embed;
}
return $item;
}
}

View File

@ -75,7 +75,7 @@ custom_timeout = false
; "" = Disabled (default) ; "" = Disabled (default)
email = "" email = ""
; Advertise a contact URL (can be any URL!) e.g. "https://t.me/elegantobjects" ; Advertise a contact Telegram url e.g. "https://t.me/elegantobjects"
telegram = "" telegram = ""
; Show Donation information for bridges if available. ; Show Donation information for bridges if available.

View File

@ -327,7 +327,7 @@ abstract class BridgeAbstract
return $this->cache->get($this->getShortName() . '_' . $key, $default); return $this->cache->get($this->getShortName() . '_' . $key, $default);
} }
protected function saveCacheValue(string $key, $value, int $ttl = 86400) protected function saveCacheValue(string $key, $value, int $ttl = null)
{ {
$this->cache->set($this->getShortName() . '_' . $key, $value, $ttl); $this->cache->set($this->getShortName() . '_' . $key, $value, $ttl);
} }

View File

@ -177,9 +177,11 @@ function getSimpleHTMLDOM(
} }
/** /**
* Fetch contents from the Internet as simplhtmldom object. Contents are cached * Gets contents from the Internet as simplhtmldom object. Contents are cached
* and re-used for subsequent calls until the cache duration elapsed. * and re-used for subsequent calls until the cache duration elapsed.
* *
* _Notice_: Cached contents are forcefully removed after 24 hours (86400 seconds).
*
* @param string $url The URL. * @param string $url The URL.
* @param int $ttl Cache duration in seconds. * @param int $ttl Cache duration in seconds.
* @param array $header (optional) A list of cURL header. * @param array $header (optional) A list of cURL header.

View File

@ -52,7 +52,7 @@
<?php if ($admin_telegram): ?> <?php if ($admin_telegram): ?>
<div> <div>
Url: <a href="<?= e($admin_telegram) ?>"><?= e($admin_telegram) ?></a> Telegram: <a href="<?= e($admin_telegram) ?>"><?= e($admin_telegram) ?></a>
</div> </div>
<?php endif; ?> <?php endif; ?>