array( 'nopic' => array( 'name' => 'Hide profile pictures', 'type' => 'checkbox', 'title' => 'Activate to hide profile pictures in content' ), 'noimg' => array( 'name' => 'Hide images in tweets', 'type' => 'checkbox', 'title' => 'Activate to hide images in tweets' ), 'noimgscaling' => array( 'name' => 'Disable image scaling', 'type' => 'checkbox', 'title' => 'Activate to disable image scaling in tweets (keeps original image)' ) ), 'By keyword or hashtag' => array( 'q' => array( 'name' => 'Keyword or #hashtag', 'required' => true, 'exampleValue' => 'rss-bridge, #rss-bridge', 'title' => << array( 'u' => array( 'name' => 'username', 'required' => true, 'exampleValue' => 'sebsauvage', 'title' => 'Insert a user name' ), 'norep' => array( 'name' => 'Without replies', 'type' => 'checkbox', 'title' => 'Only return initial tweets' ), 'noretweet' => array( 'name' => 'Without retweets', 'required' => false, 'type' => 'checkbox', 'title' => 'Hide retweets' ), 'nopinned' => array( 'name' => 'Without pinned tweet', 'required' => false, 'type' => 'checkbox', 'title' => 'Hide pinned tweet' ) ), 'By list' => array( 'user' => array( 'name' => 'User', 'required' => true, 'exampleValue' => 'sebsauvage', 'title' => 'Insert a user name' ), 'list' => array( 'name' => 'List', 'required' => true, 'title' => 'Insert the list name' ), 'filter' => array( 'name' => 'Filter', 'exampleValue' => '#rss-bridge', 'required' => false, 'title' => 'Specify term to search for' ) ), 'By list ID' => array( 'listid' => array( 'name' => 'List ID', 'exampleValue' => '31748', 'required' => true, 'title' => 'Insert the list id' ), 'filter' => array( 'name' => 'Filter', 'exampleValue' => '#rss-bridge', 'required' => false, 'title' => 'Specify term to search for' ) ) ); public function detectParameters($url){ $params = array(); // By keyword or hashtag (search) $regex = '/^(https?:\/\/)?(www\.)?twitter\.com\/search.*(\?|&)q=([^\/&?\n]+)/'; if(preg_match($regex, $url, $matches) > 0) { $params['q'] = urldecode($matches[4]); return $params; } // By hashtag $regex = '/^(https?:\/\/)?(www\.)?twitter\.com\/hashtag\/([^\/?\n]+)/'; if(preg_match($regex, $url, $matches) > 0) { $params['q'] = urldecode($matches[3]); return $params; } // By list $regex = '/^(https?:\/\/)?(www\.)?twitter\.com\/([^\/?\n]+)\/lists\/([^\/?\n]+)/'; if(preg_match($regex, $url, $matches) > 0) { $params['user'] = urldecode($matches[3]); $params['list'] = urldecode($matches[4]); return $params; } // By username $regex = '/^(https?:\/\/)?(www\.)?twitter\.com\/([^\/?\n]+)/'; if(preg_match($regex, $url, $matches) > 0) { $params['u'] = urldecode($matches[3]); return $params; } return null; } public function getName(){ switch($this->queriedContext) { case 'By keyword or hashtag': $specific = 'search '; $param = 'q'; break; case 'By username': $specific = '@'; $param = 'u'; break; case 'By list': return $this->getInput('list') . ' - Twitter list by ' . $this->getInput('user'); case 'By list ID': return 'Twitter List #' . $this->getInput('listid'); default: return parent::getName(); } return 'Twitter ' . $specific . $this->getInput($param); } public function getURI(){ switch($this->queriedContext) { case 'By keyword or hashtag': return self::URI . 'search?q=' . urlencode($this->getInput('q')) . '&f=tweets'; case 'By username': return self::URI . urlencode($this->getInput('u')); // Always return without replies! // . ($this->getInput('norep') ? '' : '/with_replies'); case 'By list': return self::URI . urlencode($this->getInput('user')) . '/lists/' . str_replace(' ', '-', strtolower($this->getInput('list'))); case 'By list ID': return self::URI . 'i/lists/' . urlencode($this->getInput('listid')); default: return parent::getURI(); } } private function getApiURI() { switch($this->queriedContext) { case 'By keyword or hashtag': return self::API_URI . '/2/search/adaptive.json?q=' . urlencode($this->getInput('q')) . '&tweet_mode=extended&tweet_search_mode=live'; case 'By username': return self::API_URI . '/2/timeline/profile/' . $this->getRestId($this->getInput('u')) . '.json?tweet_mode=extended'; case 'By list': return self::API_URI . '/2/timeline/list.json?list_id=' . $this->getListId($this->getInput('user'), $this->getInput('list')) . '&tweet_mode=extended'; case 'By list ID': return self::API_URI . '/2/timeline/list.json?list_id=' . $this->getInput('listid') . '&tweet_mode=extended'; default: returnServerError('Invalid query context !'); } } public function collectData(){ $html = ''; $page = $this->getURI(); $data = json_decode($this->getApiContents($this->getApiURI())); if(!$data) { switch($this->queriedContext) { case 'By keyword or hashtag': returnServerError('No results for this query.'); case 'By username': returnServerError('Requested username can\'t be found.'); case 'By list': returnServerError('Requested username or list can\'t be found'); } } $hidePictures = $this->getInput('nopic'); $promotedTweetIds = array_reduce($data->timeline->instructions[0]->addEntries->entries, function($carry, $entry) { if (!isset($entry->content->item)) { return $carry; } $tweet = $entry->content->item->content->tweet; if (isset($tweet->promotedMetadata)) { $carry[] = $tweet->id; } return $carry; }, array()); $hidePinned = $this->getInput('nopinned'); if ($hidePinned) { $pinnedTweetId = null; if (isset($data->timeline->instructions[1]) && isset($data->timeline->instructions[1]->pinEntry)) { $pinnedTweetId = $data->timeline->instructions[1]->pinEntry->entry->content->item->content->tweet->id; } } foreach($data->globalObjects->tweets as $tweet) { /* Debug::log('>>> ' . json_encode($tweet)); */ // Skip spurious retweets if (isset($tweet->retweeted_status_id_str) && substr($tweet->full_text, 0, 4) === 'RT @') { continue; } // Skip promoted tweets if (in_array($tweet->id_str, $promotedTweetIds)) { continue; } // Skip pinned tweet if ($hidePinned && $tweet->id_str === $pinnedTweetId) { continue; } $item = array(); // extract username and sanitize $user_info = $this->getUserInformation($tweet->user_id_str, $data->globalObjects); $item['username'] = $user_info->screen_name; $item['fullname'] = $user_info->name; $item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')'; if (null !== $this->getInput('u') && strtolower($item['username']) != strtolower($this->getInput('u'))) { $item['author'] .= ' RT: @' . $this->getInput('u'); } $item['avatar'] = $user_info->profile_image_url_https; $item['id'] = $tweet->id_str; $item['uri'] = self::URI . $item['username'] . '/status/' . $item['id']; // extract tweet timestamp $item['timestamp'] = $tweet->created_at; // Convert plain text URLs into HTML hyperlinks $cleanedTweet = $tweet->full_text; $foundUrls = false; if (isset($tweet->entities->media)) { foreach($tweet->entities->media as $media) { $cleanedTweet = str_replace($media->url, '' . $media->display_url . '', $cleanedTweet); $foundUrls = true; } } if (isset($tweet->entities->urls)) { foreach($tweet->entities->urls as $url) { $cleanedTweet = str_replace($url->url, '' . $url->display_url . '', $cleanedTweet); $foundUrls = true; } } if ($foundUrls === false) { // fallback to regex'es $reg_ex = '/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/'; if(preg_match($reg_ex, $tweet->full_text, $url)) { $cleanedTweet = preg_replace($reg_ex, "{$url[0]} ", $cleanedTweet); } } // generate the title $item['title'] = strip_tags($cleanedTweet); // Add avatar $picture_html = ''; if(!$hidePictures) { $picture_html = << {$item['username']} EOD; } // Get images $media_html = ''; if(isset($tweet->extended_entities->media) && !$this->getInput('noimg')) { foreach($tweet->extended_entities->media as $media) { switch($media->type) { case 'photo': $image = $media->media_url_https . '?name=orig'; $display_image = $media->media_url_https; // add enclosures $item['enclosures'][] = $image; $media_html .= << EOD; break; case 'video': case 'animated_gif': if(isset($media->video_info)) { $link = $media->expanded_url; $poster = $media->media_url_https; $video = null; $maxBitrate = -1; foreach($media->video_info->variants as $variant) { $bitRate = isset($variant->bitrate) ? $variant->bitrate : -100; if ($bitRate > $maxBitrate) { $maxBitrate = $bitRate; $video = $variant->url; } } if(!is_null($video)) { // add enclosures $item['enclosures'][] = $video; $item['enclosures'][] = $poster; $media_html .= <<Video