{$result->name}
+Last pushed{$lastPushed}
+Images{$releaseNotes}
+Compatibility +{$compatibility}
+License +{$license}
+Download +{$xpiFilename} ($size)
+EOD; + + $this->items[] = $item; + } + } + + public function getURI() { + if (!is_null($this->getInput('id'))) { + return self::URI . 'en-US/firefox/addon/' . $this->getInput('id') . '/versions/'; + } + + return parent::getURI(); + } + + public function getName() { + if (!empty($this->feedName)) { + return $this->feedName . ' - Firefox Add-on'; + } + + return parent::getName(); + } + + private function removeOutgoinglink($html) { + foreach ($html->find('a') as $a) { + $a->href = urldecode(preg_replace($this->outgoingRegex, '', $a->href)); + } + + return $html->innertext; + } +} diff --git a/bridges/LeBonCoinBridge.php b/bridges/LeBonCoinBridge.php index fc1432e3..372ff25c 100644 --- a/bridges/LeBonCoinBridge.php +++ b/bridges/LeBonCoinBridge.php @@ -352,12 +352,14 @@ class LeBonCoinBridge extends BridgeAbstract { public function collectData(){ - $url = 'https://api.leboncoin.fr/finder/search/'; + $url = 'https://api.leboncoin.fr/api/adfinder/v1/search'; $data = $this->buildRequestJson(); $header = array( - 'User-Agent: LBC;Android;Null;Null;Null;Null;Null;Null;Null;Null', + 'User-Agent: LBC;Android;10;SAMSUNG;phone;0aaaaaaaaaaaaaaa;wifi;8.24.3.8;152437;0', 'Content-Type: application/json', + 'X-LBC-CC: 7', + 'Accept: application/json,application/hal+json', 'Content-Length: ' . strlen($data), 'api_key: ' . self::$LBC_API_KEY ); diff --git a/bridges/NordbayernBridge.php b/bridges/NordbayernBridge.php index 37fa3d5e..08d7482a 100644 --- a/bridges/NordbayernBridge.php +++ b/bridges/NordbayernBridge.php @@ -26,7 +26,7 @@ class NordbayernBridge extends BridgeAbstract { 'Gunzenhausen' => 'gunzenhausen', 'Hersbruck' => 'hersbruck', 'Herzogenaurach' => 'herzogenaurach', - 'Hilpolstein' => 'holpolstein', + 'Hilpoltstein' => 'hilpoltstein', 'Höchstadt' => 'hoechstadt', 'Lauf' => 'lauf', 'Neumarkt' => 'neumarkt', diff --git a/bridges/RadioMelodieBridge.php b/bridges/RadioMelodieBridge.php index fb5aca6e..3df0d044 100644 --- a/bridges/RadioMelodieBridge.php +++ b/bridges/RadioMelodieBridge.php @@ -25,7 +25,7 @@ class RadioMelodieBridge extends BridgeAbstract { $picture = array(); // Get the Main picture URL - $picture[] = $this->rewriteImage($article->find('div[id=pictureTitleSupport]', 0)->find('img', 0)->src); + $picture[] = self::URI . $article->find('div[id=pictureTitleSupport]', 0)->find('img', 0)->src; $audioHTML = $article->find('audio'); // Add the audio element to the enclosure diff --git a/bridges/RedditBridge.php b/bridges/RedditBridge.php index 130dc662..1dbd8d91 100644 --- a/bridges/RedditBridge.php +++ b/bridges/RedditBridge.php @@ -23,6 +23,19 @@ class RedditBridge extends BridgeAbstract { 'exampleValue' => 'selfhosted, php', 'title' => 'SubReddit names, separated by commas' ) + ), + 'user' => array( + 'u' => array( + 'name' => 'User', + 'required' => true, + 'title' => 'User name' + ), + 'comments' => array( + 'type' => 'checkbox', + 'name' => 'Comments', + 'title' => 'Whether to return comments', + 'defaultValue' => false + ) ) ); @@ -33,12 +46,18 @@ class RedditBridge extends BridgeAbstract { public function getName() { if ($this->queriedContext == 'single') { return 'Reddit r/' . $this->getInput('r'); + } elseif ($this->queriedContext == 'user') { + return 'Reddit u/' . $this->getInput('u'); } else { return self::NAME; } } public function collectData() { + + $user = false; + $comments = false; + switch ($this->queriedContext) { case 'single': $subreddits[] = $this->getInput('r'); @@ -46,33 +65,55 @@ class RedditBridge extends BridgeAbstract { case 'multi': $subreddits = explode(',', $this->getInput('rs')); break; + case 'user': + $subreddits[] = $this->getInput('u'); + $user = true; + $comments = $this->getInput('comments'); + break; } foreach ($subreddits as $subreddit) { $name = trim($subreddit); - $values = getContents(self::URI . '/r/' . $name . '.json') + $values = getContents(self::URI . ($user ? '/user/' : '/r/') . $name . '.json') or returnServerError('Unable to fetch posts!'); $decodedValues = json_decode($values); foreach ($decodedValues->data->children as $post) { + if ($post->kind == 't1' && !$comments) { + continue; + } + $data = $post->data; $item = array(); $item['author'] = $data->author; - $item['title'] = $data->title; $item['uid'] = $data->id; $item['timestamp'] = $data->created_utc; $item['uri'] = $this->encodePermalink($data->permalink); $item['categories'] = array(); - $item['categories'][] = $data->link_flair_text; - $item['categories'][] = $data->pinned ? 'Pinned' : null; + + if ($post->kind == 't1') { + $item['title'] = 'Comment: ' . $data->link_title; + } else { + $item['title'] = $data->title; + + $item['categories'][] = $data->link_flair_text; + $item['categories'][] = $data->pinned ? 'Pinned' : null; + $item['categories'][] = $data->spoiler ? 'Spoiler' : null; + } + $item['categories'][] = $data->over_18 ? 'NSFW' : null; - $item['categories'][] = $data->spoiler ? 'Spoiler' : null; $item['categories'] = array_filter($item['categories']); - if ($data->is_self) { + if ($post->kind == 't1') { + // Comment + + $item['content'] + = htmlspecialchars_decode($data->body_html); + + } elseif ($data->is_self) { // Text post $item['content'] @@ -112,7 +153,7 @@ class RedditBridge extends BridgeAbstract { $id = $media->media_id; $type = $data->media_metadata->$id->m == 'image/gif' ? 'gif' : 'u'; $src = $data->media_metadata->$id->s->$type; - $images[] = ''; + $images[] = ''; } $item['content'] = implode('', $images); 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 = "$data
"; + break; + case 'heading': + $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/SoundcloudBridge.php b/bridges/SoundcloudBridge.php index 45e6fed1..fe1e9414 100644 --- a/bridges/SoundcloudBridge.php +++ b/bridges/SoundcloudBridge.php @@ -27,6 +27,9 @@ class SoundCloudBridge extends BridgeAbstract { private $feedIcon = null; private $clientIDCache = null; + private $clientIdRegex = '/client_id.*?"(.+?)"/'; + private $widgetRegex = '/widget-.+?\.js/'; + public function collectData(){ $res = $this->apiGet('resolve', array( 'url' => 'https://soundcloud.com/' . $this->getInput('u') @@ -112,21 +115,32 @@ class SoundCloudBridge extends BridgeAbstract { // Without url=http, this returns a 404 $playerHTML = getContents('https://w.soundcloud.com/player/?url=http') - or returnServerError('Unable to get player page.'); - $regex = '/widget-.+?\.js/'; - if(preg_match($regex, $playerHTML, $matches) == false) + or returnServerError('Unable to get player page.'); + + // Extract widget JS filenames from player page + if(preg_match_all($this->widgetRegex, $playerHTML, $matches) == false) returnServerError('Unable to find widget JS URL.'); - $widgetURL = 'https://widget.sndcdn.com/' . $matches[0]; - $widgetJS = getContents($widgetURL) - or returnServerError('Unable to get widget JS page.'); - $regex = '/client_id.*?"(.+?)"/'; - if(preg_match($regex, $widgetJS, $matches) == false) + $clientID = ''; + + // Loop widget js files and extract client ID + foreach ($matches[0] as $widgetFile) { + $widgetURL = 'https://widget.sndcdn.com/' . $widgetFile; + + $widgetJS = getContents($widgetURL) + or returnServerError('Unable to get widget JS page.'); + + if(preg_match($this->clientIdRegex, $widgetJS, $matches)) { + $clientID = $matches[1]; + $this->clientIDCache->saveData($clientID); + + return $clientID; + } + } + + if (empty($clientID)) { returnServerError('Unable to find client ID.'); - $clientID = $matches[1]; - - $this->clientIDCache->saveData($clientID); - return $clientID; + } } private function buildAPIURL($endpoint, $parameters){ diff --git a/bridges/SymfonyCastsBridge.php b/bridges/SymfonyCastsBridge.php new file mode 100644 index 00000000..acad9041 --- /dev/null +++ b/bridges/SymfonyCastsBridge.php @@ -0,0 +1,34 @@ +find('div'); + + /* @var simple_html_dom $div */ + foreach ($dives as $div) { + $id = $div->getAttribute('data-mark-update-id-value'); + $type = $div->find('h5', 0); + $title = $div->find('span', 0); + $dateString = $div->find('h5.font-gray', 0); + $href = $div->find('a', 0); + $url = 'https://symfonycasts.com' . $href->getAttribute('href'); + + $item = array(); // Create an empty item + $item['uid'] = $id; + $item['title'] = $title->innertext; + $item['timestamp'] = $dateString->innertext; + $item['content'] = $type->plaintext . '' . $title . ''; + $item['uri'] = $url; + $this->items[] = $item; // Add item to the list + } + + } +} diff --git a/bridges/TelegramBridge.php b/bridges/TelegramBridge.php index a7296b8a..152e2da0 100644 --- a/bridges/TelegramBridge.php +++ b/bridges/TelegramBridge.php @@ -21,6 +21,18 @@ class TelegramBridge extends BridgeAbstract { private $itemTitle = ''; private $backgroundImageRegex = "/background-image:url\('(.*)'\)/"; + private $detectParamsRegex = '/^https?:\/\/t.me\/(?:s\/)?([\w]+)$/'; + + public function detectParameters($url) { + $params = array(); + + if(preg_match($this->detectParamsRegex, $url, $matches) > 0) { + $params['username'] = $matches[1]; + return $params; + } + + return null; + } public function collectData() { diff --git a/bridges/TheYeteeBridge.php b/bridges/TheYeteeBridge.php index fa5a6455..fb3c969e 100644 --- a/bridges/TheYeteeBridge.php +++ b/bridges/TheYeteeBridge.php @@ -12,7 +12,7 @@ class TheYeteeBridge extends BridgeAbstract { $html = getSimpleHTMLDOM(self::URI) or returnServerError('Could not request The Yetee.'); - $div = $html->find('.hero-col'); + $div = $html->find('.module_timed-item.is--full'); foreach($div as $element) { $item = array(); @@ -21,16 +21,15 @@ class TheYeteeBridge extends BridgeAbstract { $title = $element->find('h2', 0)->plaintext; $item['title'] = $title; - $author = trim($element->find('div[class=credit]', 0)->plaintext); + $author = trim($element->find('.module_timed-item--artist a', 0)->plaintext); $item['author'] = $author; - $uri = $element->find('div[class=controls] a', 0)->href; - $item['uri'] = static::URI . $uri; + $item['uri'] = static::URI; - $content = '' . $element->find('section[class=product-listing-info] p', -1)->plaintext . '
'; - $photos = $element->find('a[class=js-modaal-gallery] img'); + $content = '' . $title . ' by ' . $author . '
'; + $photos = $element->find('a.img'); foreach($photos as $photo) { - $content = $content . "