0
0
mirror of https://github.com/RSS-Bridge/rss-bridge.git synced 2025-06-29 18:21:07 +00:00

Compare commits

...

8 Commits

Author SHA1 Message Date
André Andersson
dee734d360
Add Auctionet bridge (#4452) 2025-03-05 19:41:24 +01:00
Latz
744f996224
Added bridge for Toms Touché (https://taz.de/#!tom=tomdestages) (#4438) 2025-03-05 19:39:18 +01:00
Pavel Korytov
f270cd35e7
[TldrTechBridge] Fix duplicate entries and empty sections (#4466) 2025-03-05 19:36:41 +01:00
Tomasz Molski
83c36a87e2
[ReutersBridge] Adjust Fact Check feed path (#4465) 2025-03-05 19:35:12 +01:00
Tomasz Molski
810e17b556
feat: added LeagueOfLegendsNewsBridge (#4462) 2025-03-05 19:34:35 +01:00
sysadminstory
97f07cf216
[InstagramBridge] Add a fallback to the "Username" mode (#4461)
- Added some header that could help Instagram to not block RSS Bridge
- Added a fallback function to use the "Embed profile" Instagram feature
  to get the content shared by one Instagram user
2025-03-05 19:32:03 +01:00
sysadminstory
62fafdc24b
[FreeTelechargerBridge] Update URL and some fix (#4459)
- Updated the URL to the new URL in the bridge Meta Data
- Use an other URL that seems to permit to bypass CF protection
  (sometimes)
2025-03-05 19:30:38 +01:00
sysadminstory
cd4cdcfd65
[RadioMelodieBridge] Fix media content (#4458)
- Fix the audio source with the absolute URL
- Fix the pictture enclosure URL (those are already absolute URL)
2025-03-05 19:30:09 +01:00
8 changed files with 548 additions and 13 deletions

344
bridges/AuctionetBridge.php Normal file
View File

@ -0,0 +1,344 @@
<?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

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

View File

@ -86,6 +86,11 @@ class InstagramBridge extends BridgeAbstract
$headers = [];
$sessionId = $this->getOption('session_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) {
$headers[] = 'cookie: sessionid=' . $sessionId . '; ds_user_id=' . $dsUserId;
}
@ -125,8 +130,10 @@ class InstagramBridge extends BridgeAbstract
return;
}
if (!is_null($this->getInput('u'))) {
if (!is_null($this->getInput('u')) && !$this->fallbackMode) {
$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'))) {
$userMedia = $data->data->hashtag->edge_hashtag_to_media->edges;
} elseif (!is_null($this->getInput('l'))) {
@ -134,7 +141,12 @@ class InstagramBridge extends BridgeAbstract
}
foreach ($userMedia as $media) {
$media = $media->node;
// The media is not in the same element if in fallback mode than not
if (!$this->fallbackMode) {
$media = $media->node;
} else {
$media = $media->shortcode_media;
}
switch ($this->getInput('media_type')) {
case 'all':
@ -267,14 +279,39 @@ class InstagramBridge extends BridgeAbstract
protected function getInstagramJSON($uri)
{
// Sets fallbackMode to false
$this->fallbackMode = false;
if (!is_null($this->getInput('u'))) {
$userId = $this->getInstagramUserId($this->getInput('u'));
$data = $this->getContents(self::URI .
try {
$userId = $this->getInstagramUserId($this->getInput('u'));
$data = $this->getContents(self::URI .
'graphql/query/?query_hash=' .
self::USER_QUERY_HASH .
'&variables={"id"%3A"' .
$userId .
'"%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);
} elseif (!is_null($this->getInput('h'))) {
$data = $this->getContents(self::URI .

View File

@ -0,0 +1,118 @@
<?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

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

View File

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

View File

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

View File

@ -0,0 +1,28 @@
<?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;
}
}