diff --git a/bridges/Arte7Bridge.php b/bridges/Arte7Bridge.php index 562f648f..08390afa 100644 --- a/bridges/Arte7Bridge.php +++ b/bridges/Arte7Bridge.php @@ -1,7 +1,7 @@ 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 ? "
$title" : $title, + 'enclosures' => array_slice($images, 1), + ); + } + } + + public function getName() + { + return $this->title ?: parent::getName(); + } +} diff --git a/bridges/ChristianDailyReporterBridge.php b/bridges/ChristianDailyReporterBridge.php deleted file mode 100644 index 85f664df..00000000 --- a/bridges/ChristianDailyReporterBridge.php +++ /dev/null @@ -1,28 +0,0 @@ -find('div.top p a,div.column p a') as $element) { - $item = array(); - // Title - $item['title'] = $element->innertext; - // URL - $item['uri'] = $element->href; - $this->items[] = $item; - } - } -} diff --git a/bridges/DribbbleBridge.php b/bridges/DribbbleBridge.php index 01cfb21a..e3452658 100644 --- a/bridges/DribbbleBridge.php +++ b/bridges/DribbbleBridge.php @@ -86,7 +86,7 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico'; private function getImageTag($preview_path, $title){ return sprintf( - '
%s', + '
%s', $this->getFullSizeImagePath($preview_path), $preview_path, $title @@ -94,6 +94,11 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico'; } private function getFullSizeImagePath($preview_path){ - return explode('?compress=1', $preview_path)[0]; + // Get last image from srcset + $src_set_urls = explode(',', $preview_path); + $url = end($src_set_urls); + $url = explode(' ', $url)[1]; + + return htmlspecialchars_decode($url); } } diff --git a/bridges/ExtremeDownloadBridge.php b/bridges/ExtremeDownloadBridge.php index bca3997a..9859a2a6 100644 --- a/bridges/ExtremeDownloadBridge.php +++ b/bridges/ExtremeDownloadBridge.php @@ -1,7 +1,7 @@ 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; + } +} diff --git a/bridges/GithubIssueBridge.php b/bridges/GithubIssueBridge.php index 29a336bd..46fbc4c1 100644 --- a/bridges/GithubIssueBridge.php +++ b/bridges/GithubIssueBridge.php @@ -109,8 +109,7 @@ class GithubIssueBridge extends BridgeAbstract { } private function extractIssueComment($issueNbr, $title, $comment){ - - $uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->parent->id); + $uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->id); $author = $comment->find('.author', 0)->plaintext; @@ -171,9 +170,9 @@ class GithubIssueBridge extends BridgeAbstract { case 'Project Issues': foreach($html->find('.js-active-navigation-container .js-navigation-item') as $issue) { $info = $issue->find('.opened-by', 0); - $issueNbr = substr( - trim($info->plaintext), 1, strpos(trim($info->plaintext), ' ') - ); + + preg_match('/\/([0-9]+)$/', $issue->find('a', 0)->href, $match); + $issueNbr = $match[1]; $item = array(); $item['content'] = ''; diff --git a/bridges/ReutersBridge.php b/bridges/ReutersBridge.php new file mode 100644 index 00000000..cb6b4e38 --- /dev/null +++ b/bridges/ReutersBridge.php @@ -0,0 +1,246 @@ + 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_caption = "
$image_caption
"; + $figure = "
$img \t $img_caption
"; + $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 . "

$data

"; + break; + case 'heading': + $description = $description . "

$data

"; + break; + case 'infographics': + $description = $description . ""; + break; + case 'inline_items': + $item_list = $content['items']; + $description = $description . '

'; + foreach ($item_list as $item) { + if($item['type'] == 'text') { + $description = $description . $item['content']; + } else { + $description = $description . $item['symbol']; + } + } + $description = $description . '

'; + 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; + } + } +} diff --git a/bridges/ZoneTelechargementBridge.php b/bridges/ZoneTelechargementBridge.php index 06103df0..56c6764a 100644 --- a/bridges/ZoneTelechargementBridge.php +++ b/bridges/ZoneTelechargementBridge.php @@ -8,7 +8,7 @@ class ZoneTelechargementBridge extends BridgeAbstract { */ const NAME = 'Zone Telechargement'; - const URI = 'https://www.zt-za.com/'; + const URI = 'https://www.zt-za.net/'; const DESCRIPTION = 'Suivi de série sur Zone Telechargement'; const MAINTAINER = 'sysadminstory'; const PARAMETERS = array( @@ -34,17 +34,17 @@ class ZoneTelechargementBridge extends BridgeAbstract { ); // This is an URL that is not protected by robot protection for Direct Download - const UNPROTECED_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 - const UNPROTECED_URI_STREAMING = 'https://zone-telechargement.stream/'; + const UNPROTECTED_URI_STREAMING = 'https://zone-telechargement.stream/'; public function getIcon() { - return self::UNPROTECED_URI . '/templates/Default/images/favicon.ico'; + return self::UNPROTECTED_URI . '/templates/Default/images/favicon.ico'; } public function collectData(){ - $html = getSimpleHTMLDOM(self::UNPROTECED_URI . $this->getInput('url')) + $html = getSimpleHTMLDOM(self::UNPROTECTED_URI . $this->getInput('url')) or returnServerError('Could not request Zone Telechargement.'); $filter = $this->getInput('filter'); @@ -79,7 +79,7 @@ class ZoneTelechargementBridge extends BridgeAbstract { // Handle the Streaming links if($filter == 'both' || $filter == 'streaming') { // Get the post content, on the dedicated streaming website - $htmlstreaming = getSimpleHTMLDOM(self::UNPROTECED_URI_STREAMING . $this->getInput('url')) + $htmlstreaming = getSimpleHTMLDOM(self::UNPROTECTED_URI_STREAMING . $this->getInput('url')) or returnServerError('Could not request Zone Telechargement.'); // Get the HTML element containing all the links $streaminglinkshtml = $htmlstreaming->find('p[style=background-color: #FECC00;]', 1)->parent()->next_sibling(); diff --git a/lib/BridgeCard.php b/lib/BridgeCard.php index 0ed605bf..69c67bc4 100644 --- a/lib/BridgeCard.php +++ b/lib/BridgeCard.php @@ -347,11 +347,13 @@ This bridge is not fetching its content through a secure connection'; CARD; // If we don't have any parameter for the bridge, we print a generic form to load it. - if(count($parameters) === 0 - || count($parameters) === 1 && array_key_exists('global', $parameters)) { - + if (count($parameters) === 0) { $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 { foreach($parameters as $parameterName => $parameter) {