aboutsummaryrefslogtreecommitdiff
path: root/bridges/TwitterBridge.php
diff options
context:
space:
mode:
Diffstat (limited to 'bridges/TwitterBridge.php')
-rw-r--r--bridges/TwitterBridge.php1181
1 files changed, 600 insertions, 581 deletions
diff --git a/bridges/TwitterBridge.php b/bridges/TwitterBridge.php
index 71ac52ba..743843da 100644
--- a/bridges/TwitterBridge.php
+++ b/bridges/TwitterBridge.php
@@ -1,37 +1,39 @@
<?php
-class TwitterBridge extends BridgeAbstract {
- const NAME = 'Twitter Bridge';
- const URI = 'https://twitter.com/';
- const API_URI = 'https://api.twitter.com';
- const GUEST_TOKEN_USES = 100;
- const GUEST_TOKEN_EXPIRY = 10800; // 3hrs
- const CACHE_TIMEOUT = 300; // 5min
- const DESCRIPTION = 'returns tweets';
- const MAINTAINER = 'arnd-s';
- const PARAMETERS = array(
- 'global' => 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 OR rssbridge',
- 'title' => <<<EOD
+
+class TwitterBridge extends BridgeAbstract
+{
+ const NAME = 'Twitter Bridge';
+ const URI = 'https://twitter.com/';
+ const API_URI = 'https://api.twitter.com';
+ const GUEST_TOKEN_USES = 100;
+ const GUEST_TOKEN_EXPIRY = 10800; // 3hrs
+ const CACHE_TIMEOUT = 300; // 5min
+ const DESCRIPTION = 'returns tweets';
+ const MAINTAINER = 'arnd-s';
+ const PARAMETERS = [
+ 'global' => [
+ 'nopic' => [
+ 'name' => 'Hide profile pictures',
+ 'type' => 'checkbox',
+ 'title' => 'Activate to hide profile pictures in content'
+ ],
+ 'noimg' => [
+ 'name' => 'Hide images in tweets',
+ 'type' => 'checkbox',
+ 'title' => 'Activate to hide images in tweets'
+ ],
+ 'noimgscaling' => [
+ 'name' => 'Disable image scaling',
+ 'type' => 'checkbox',
+ 'title' => 'Activate to disable image scaling in tweets (keeps original image)'
+ ]
+ ],
+ 'By keyword or hashtag' => [
+ 'q' => [
+ 'name' => 'Keyword or #hashtag',
+ 'required' => true,
+ 'exampleValue' => 'rss-bridge OR rssbridge',
+ 'title' => <<<EOD
* To search for multiple words (must contain all of these words), put a space between them.
Example: `rss-bridge release`.
@@ -56,328 +58,340 @@ Example: `#rss-bridge OR #rssbridge`
Example: `#rss-bridge OR #rssbridge -release`
EOD
- )
- ),
- 'By username' => 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' => 'Scobleizer',
- 'title' => 'Insert a user name'
- ),
- 'list' => array(
- 'name' => 'List',
- 'required' => true,
- 'exampleValue' => 'Tech-News',
- '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'
- )
- )
- );
-
- private $apiKey = null;
- private $guestToken = null;
- private $authHeader = array();
-
- 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();
- }
- }
-
- public function collectData(){
- // $data will contain an array of all found tweets (unfiltered)
- $data = null;
- // Contains user data (when in by username context)
- $user = null;
- // Array of all found tweets
- $tweets = array();
-
- // Get authentication information
- $this->getApiKey();
-
- // Try to get all tweets
- switch($this->queriedContext) {
- case 'By username':
- $user = $this->makeApiCall('/1.1/users/show.json', array('screen_name' => $this->getInput('u')));
- if (!$user) {
- returnServerError('Requested username can\'t be found.');
- }
-
- $params = array(
- 'user_id' => $user->id_str,
- 'tweet_mode' => 'extended'
- );
-
- $data = $this->makeApiCall('/1.1/statuses/user_timeline.json', $params);
- break;
-
- case 'By keyword or hashtag':
- $params = array(
- 'q' => urlencode($this->getInput('q')),
- 'tweet_mode' => 'extended',
- 'tweet_search_mode' => 'live',
- );
-
- $data = $this->makeApiCall('/1.1/search/tweets.json', $params)->statuses;
- break;
-
- case 'By list':
- $params = array(
- 'slug' => strtolower($this->getInput('list')),
- 'owner_screen_name' => strtolower($this->getInput('user')),
- 'tweet_mode' => 'extended',
- );
-
- $data = $this->makeApiCall('/1.1/lists/statuses.json', $params);
- break;
-
- case 'By list ID':
- $params = array(
- 'list_id' => $this->getInput('listid'),
- 'tweet_mode' => 'extended',
- );
-
- $data = $this->makeApiCall('/1.1/lists/statuses.json', $params);
- break;
-
- default:
- returnServerError('Invalid query context !');
- }
-
- if(!$data) {
- switch($this->queriedContext) {
- case 'By keyword or hashtag':
- returnServerError('No results for this query.');
- // fall-through
- case 'By username':
- returnServerError('Requested username can\'t be found.');
- // fall-through
- case 'By list':
- returnServerError('Requested username or list can\'t be found');
- }
- }
-
- // Filter out unwanted tweets
- foreach ($data as $tweet) {
- // Filter out retweets to remove possible duplicates of original tweet
- switch($this->queriedContext) {
- case 'By keyword or hashtag':
- if (isset($tweet->retweeted_status) && substr($tweet->full_text, 0, 4) === 'RT @') {
- continue 2;
- }
- break;
- }
- $tweets[] = $tweet;
- }
-
- $hidePictures = $this->getInput('nopic');
-
- $hidePinned = $this->getInput('nopinned');
- if ($hidePinned) {
- $pinnedTweetId = null;
- if ($user && $user->pinned_tweet_ids_str) {
- $pinnedTweetId = $user->pinned_tweet_ids_str;
- }
- }
-
- foreach($tweets as $tweet) {
-
- // Skip own Retweets...
- if (isset($tweet->retweeted_status) && $tweet->retweeted_status->user->id_str === $tweet->user->id_str) {
- continue;
- }
-
- // Skip pinned tweet
- if ($hidePinned && $tweet->id_str === $pinnedTweetId) {
- continue;
- }
-
- switch($this->queriedContext) {
- case 'By username':
- if ($this->getInput('norep') && isset($tweet->in_reply_to_status_id))
- continue 2;
- break;
- }
-
- $item = array();
-
- $realtweet = $tweet;
- if (isset($tweet->retweeted_status)) {
- // Tweet is a Retweet, so set author based on original tweet and set realtweet for reference to the right content
- $realtweet = $tweet->retweeted_status;
- }
-
- $item['username'] = $realtweet->user->screen_name;
- $item['fullname'] = $realtweet->user->name;
- $item['avatar'] = $realtweet->user->profile_image_url_https;
- $item['timestamp'] = $realtweet->created_at;
- $item['id'] = $realtweet->id_str;
- $item['uri'] = self::URI . $item['username'] . '/status/' . $item['id'];
- $item['author'] = (isset($tweet->retweeted_status) ? 'RT: ' : '' )
- . $item['fullname']
- . ' (@'
- . $item['username'] . ')';
-
- // Convert plain text URLs into HTML hyperlinks
- $fulltext = $realtweet->full_text;
- $cleanedTweet = $fulltext;
-
- $foundUrls = false;
-
- if (substr($cleanedTweet, 0, 4) === 'RT @') {
- $cleanedTweet = substr($cleanedTweet, 3);
- }
-
- if (isset($realtweet->entities->media)) {
- foreach($realtweet->entities->media as $media) {
- $cleanedTweet = str_replace($media->url,
- '<a href="' . $media->expanded_url . '">' . $media->display_url . '</a>',
- $cleanedTweet);
- $foundUrls = true;
- }
- }
- if (isset($realtweet->entities->urls)) {
- foreach($realtweet->entities->urls as $url) {
- $cleanedTweet = str_replace($url->url,
- '<a href="' . $url->expanded_url . '">' . $url->display_url . '</a>',
- $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, $realtweet->full_text, $url)) {
- $cleanedTweet = preg_replace($reg_ex,
- "<a href='{$url[0]}' target='_blank'>{$url[0]}</a> ",
- $cleanedTweet);
- }
- }
- // generate the title
- $item['title'] = strip_tags($cleanedTweet);
-
- // Add avatar
- $picture_html = '';
- if(!$hidePictures) {
- $picture_html = <<<EOD
+ ]
+ ],
+ 'By username' => [
+ 'u' => [
+ 'name' => 'username',
+ 'required' => true,
+ 'exampleValue' => 'sebsauvage',
+ 'title' => 'Insert a user name'
+ ],
+ 'norep' => [
+ 'name' => 'Without replies',
+ 'type' => 'checkbox',
+ 'title' => 'Only return initial tweets'
+ ],
+ 'noretweet' => [
+ 'name' => 'Without retweets',
+ 'required' => false,
+ 'type' => 'checkbox',
+ 'title' => 'Hide retweets'
+ ],
+ 'nopinned' => [
+ 'name' => 'Without pinned tweet',
+ 'required' => false,
+ 'type' => 'checkbox',
+ 'title' => 'Hide pinned tweet'
+ ]
+ ],
+ 'By list' => [
+ 'user' => [
+ 'name' => 'User',
+ 'required' => true,
+ 'exampleValue' => 'Scobleizer',
+ 'title' => 'Insert a user name'
+ ],
+ 'list' => [
+ 'name' => 'List',
+ 'required' => true,
+ 'exampleValue' => 'Tech-News',
+ 'title' => 'Insert the list name'
+ ],
+ 'filter' => [
+ 'name' => 'Filter',
+ 'exampleValue' => '#rss-bridge',
+ 'required' => false,
+ 'title' => 'Specify term to search for'
+ ]
+ ],
+ 'By list ID' => [
+ 'listid' => [
+ 'name' => 'List ID',
+ 'exampleValue' => '31748',
+ 'required' => true,
+ 'title' => 'Insert the list id'
+ ],
+ 'filter' => [
+ 'name' => 'Filter',
+ 'exampleValue' => '#rss-bridge',
+ 'required' => false,
+ 'title' => 'Specify term to search for'
+ ]
+ ]
+ ];
+
+ private $apiKey = null;
+ private $guestToken = null;
+ private $authHeader = [];
+
+ public function detectParameters($url)
+ {
+ $params = [];
+
+ // 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();
+ }
+ }
+
+ public function collectData()
+ {
+ // $data will contain an array of all found tweets (unfiltered)
+ $data = null;
+ // Contains user data (when in by username context)
+ $user = null;
+ // Array of all found tweets
+ $tweets = [];
+
+ // Get authentication information
+ $this->getApiKey();
+
+ // Try to get all tweets
+ switch ($this->queriedContext) {
+ case 'By username':
+ $user = $this->makeApiCall('/1.1/users/show.json', ['screen_name' => $this->getInput('u')]);
+ if (!$user) {
+ returnServerError('Requested username can\'t be found.');
+ }
+
+ $params = [
+ 'user_id' => $user->id_str,
+ 'tweet_mode' => 'extended'
+ ];
+
+ $data = $this->makeApiCall('/1.1/statuses/user_timeline.json', $params);
+ break;
+
+ case 'By keyword or hashtag':
+ $params = [
+ 'q' => urlencode($this->getInput('q')),
+ 'tweet_mode' => 'extended',
+ 'tweet_search_mode' => 'live',
+ ];
+
+ $data = $this->makeApiCall('/1.1/search/tweets.json', $params)->statuses;
+ break;
+
+ case 'By list':
+ $params = [
+ 'slug' => strtolower($this->getInput('list')),
+ 'owner_screen_name' => strtolower($this->getInput('user')),
+ 'tweet_mode' => 'extended',
+ ];
+
+ $data = $this->makeApiCall('/1.1/lists/statuses.json', $params);
+ break;
+
+ case 'By list ID':
+ $params = [
+ 'list_id' => $this->getInput('listid'),
+ 'tweet_mode' => 'extended',
+ ];
+
+ $data = $this->makeApiCall('/1.1/lists/statuses.json', $params);
+ break;
+
+ default:
+ returnServerError('Invalid query context !');
+ }
+
+ if (!$data) {
+ switch ($this->queriedContext) {
+ case 'By keyword or hashtag':
+ returnServerError('No results for this query.');
+ // fall-through
+ case 'By username':
+ returnServerError('Requested username can\'t be found.');
+ // fall-through
+ case 'By list':
+ returnServerError('Requested username or list can\'t be found');
+ }
+ }
+
+ // Filter out unwanted tweets
+ foreach ($data as $tweet) {
+ // Filter out retweets to remove possible duplicates of original tweet
+ switch ($this->queriedContext) {
+ case 'By keyword or hashtag':
+ if (isset($tweet->retweeted_status) && substr($tweet->full_text, 0, 4) === 'RT @') {
+ continue 2;
+ }
+ break;
+ }
+ $tweets[] = $tweet;
+ }
+
+ $hidePictures = $this->getInput('nopic');
+
+ $hidePinned = $this->getInput('nopinned');
+ if ($hidePinned) {
+ $pinnedTweetId = null;
+ if ($user && $user->pinned_tweet_ids_str) {
+ $pinnedTweetId = $user->pinned_tweet_ids_str;
+ }
+ }
+
+ foreach ($tweets as $tweet) {
+ // Skip own Retweets...
+ if (isset($tweet->retweeted_status) && $tweet->retweeted_status->user->id_str === $tweet->user->id_str) {
+ continue;
+ }
+
+ // Skip pinned tweet
+ if ($hidePinned && $tweet->id_str === $pinnedTweetId) {
+ continue;
+ }
+
+ switch ($this->queriedContext) {
+ case 'By username':
+ if ($this->getInput('norep') && isset($tweet->in_reply_to_status_id)) {
+ continue 2;
+ }
+ break;
+ }
+
+ $item = [];
+
+ $realtweet = $tweet;
+ if (isset($tweet->retweeted_status)) {
+ // Tweet is a Retweet, so set author based on original tweet and set realtweet for reference to the right content
+ $realtweet = $tweet->retweeted_status;
+ }
+
+ $item['username'] = $realtweet->user->screen_name;
+ $item['fullname'] = $realtweet->user->name;
+ $item['avatar'] = $realtweet->user->profile_image_url_https;
+ $item['timestamp'] = $realtweet->created_at;
+ $item['id'] = $realtweet->id_str;
+ $item['uri'] = self::URI . $item['username'] . '/status/' . $item['id'];
+ $item['author'] = (isset($tweet->retweeted_status) ? 'RT: ' : '' )
+ . $item['fullname']
+ . ' (@'
+ . $item['username'] . ')';
+
+ // Convert plain text URLs into HTML hyperlinks
+ $fulltext = $realtweet->full_text;
+ $cleanedTweet = $fulltext;
+
+ $foundUrls = false;
+
+ if (substr($cleanedTweet, 0, 4) === 'RT @') {
+ $cleanedTweet = substr($cleanedTweet, 3);
+ }
+
+ if (isset($realtweet->entities->media)) {
+ foreach ($realtweet->entities->media as $media) {
+ $cleanedTweet = str_replace(
+ $media->url,
+ '<a href="' . $media->expanded_url . '">' . $media->display_url . '</a>',
+ $cleanedTweet
+ );
+ $foundUrls = true;
+ }
+ }
+ if (isset($realtweet->entities->urls)) {
+ foreach ($realtweet->entities->urls as $url) {
+ $cleanedTweet = str_replace(
+ $url->url,
+ '<a href="' . $url->expanded_url . '">' . $url->display_url . '</a>',
+ $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, $realtweet->full_text, $url)) {
+ $cleanedTweet = preg_replace(
+ $reg_ex,
+ "<a href='{$url[0]}' target='_blank'>{$url[0]}</a> ",
+ $cleanedTweet
+ );
+ }
+ }
+ // generate the title
+ $item['title'] = strip_tags($cleanedTweet);
+
+ // Add avatar
+ $picture_html = '';
+ if (!$hidePictures) {
+ $picture_html = <<<EOD
<a href="https://twitter.com/{$item['username']}">
<img
style="align:top; width:75px; border:1px solid black;"
@@ -386,20 +400,20 @@ EOD
title="{$item['fullname']}" />
</a>
EOD;
- }
-
- // Get images
- $media_html = '';
- if(isset($realtweet->extended_entities->media) && !$this->getInput('noimg')) {
- foreach($realtweet->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
+ }
+
+ // Get images
+ $media_html = '';
+ if (isset($realtweet->extended_entities->media) && !$this->getInput('noimg')) {
+ foreach ($realtweet->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
<a href="{$image}">
<img
style="align:top; max-width:558px; border:1px solid black;"
@@ -407,61 +421,61 @@ EOD;
src="{$display_image}" />
</a>
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 .= <<<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 .= <<<EOD
<a href="{$link}">Video</a>
<video
style="align:top; max-width:558px; border:1px solid black;"
referrerpolicy="no-referrer"
src="{$video}" poster="{$poster}" />
EOD;
- }
- }
- break;
- default:
- Debug::log('Missing support for media type: ' . $media->type);
- }
- }
- }
-
- switch($this->queriedContext) {
- case 'By list':
- case 'By list ID':
- // Check if filter applies to list (using raw content)
- if($this->getInput('filter')) {
- if(stripos($cleanedTweet, $this->getInput('filter')) === false) {
- continue 2; // switch + for-loop!
- }
- }
- break;
- case 'By username':
- if ($this->getInput('noretweet') && strtolower($item['username']) != strtolower($this->getInput('u'))) {
- continue 2; // switch + for-loop!
- }
- break;
- default:
- }
-
- $item['content'] = <<<EOD
+ }
+ }
+ break;
+ default:
+ Debug::log('Missing support for media type: ' . $media->type);
+ }
+ }
+ }
+
+ switch ($this->queriedContext) {
+ case 'By list':
+ case 'By list ID':
+ // Check if filter applies to list (using raw content)
+ if ($this->getInput('filter')) {
+ if (stripos($cleanedTweet, $this->getInput('filter')) === false) {
+ continue 2; // switch + for-loop!
+ }
+ }
+ break;
+ case 'By username':
+ if ($this->getInput('noretweet') && strtolower($item['username']) != strtolower($this->getInput('u'))) {
+ continue 2; // switch + for-loop!
+ }
+ break;
+ default:
+ }
+
+ $item['content'] = <<<EOD
<div style="display: inline-block; vertical-align: top;">
{$picture_html}
</div>
@@ -473,173 +487,178 @@ EOD;
</div>
EOD;
- // put out
- $this->items[] = $item;
- }
-
- usort($this->items, array('TwitterBridge', 'compareTweetId'));
- }
-
- private static function compareTweetId($tweet1, $tweet2) {
- return (intval($tweet1['id']) < intval($tweet2['id']) ? 1 : -1);
- }
-
- //The aim of this function is to get an API key and a guest token
- //This function takes 2 requests, and therefore is cached
- private function getApiKey($forceNew = 0) {
-
- $cacheFac = new CacheFactory();
-
- $r_cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
- $r_cache->setScope(get_called_class());
- $r_cache->setKey(array('refresh'));
- $data = $r_cache->loadData();
-
- $refresh = null;
- if($data === null) {
- $refresh = time();
- $r_cache->saveData($refresh);
- } else {
- $refresh = $data;
- }
-
- $cacheFac = new CacheFactory();
-
- $cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
- $cache->setScope(get_called_class());
- $cache->setKey(array('api_key'));
- $data = $cache->loadData();
-
- $apiKey = null;
- if($forceNew || $data === null || (time() - $refresh) > self::GUEST_TOKEN_EXPIRY) {
- $twitterPage = getContents('https://twitter.com');
-
- $jsLink = false;
- $jsMainRegexArray = array(
- '/(https:\/\/abs\.twimg\.com\/responsive-web\/web\/main\.[^\.]+\.js)/m',
- '/(https:\/\/abs\.twimg\.com\/responsive-web\/web_legacy\/main\.[^\.]+\.js)/m',
- '/(https:\/\/abs\.twimg\.com\/responsive-web\/client-web\/main\.[^\.]+\.js)/m',
- '/(https:\/\/abs\.twimg\.com\/responsive-web\/client-web-legacy\/main\.[^\.]+\.js)/m',
- );
- foreach ($jsMainRegexArray as $jsMainRegex) {
- if (preg_match_all($jsMainRegex, $twitterPage, $jsMainMatches, PREG_SET_ORDER, 0)) {
- $jsLink = $jsMainMatches[0][0];
- break;
- }
- }
- if (!$jsLink) {
- returnServerError('Could not locate main.js link');
- }
-
- $jsContent = getContents($jsLink);
- $apiKeyRegex = '/([a-zA-Z0-9]{59}%[a-zA-Z0-9]{44})/m';
- preg_match_all($apiKeyRegex, $jsContent, $apiKeyMatches, PREG_SET_ORDER, 0);
- $apiKey = $apiKeyMatches[0][0];
- $cache->saveData($apiKey);
- } else {
- $apiKey = $data;
- }
-
- $cacheFac2 = new CacheFactory();
-
- $gt_cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
- $gt_cache->setScope(get_called_class());
- $gt_cache->setKey(array('guest_token'));
- $guestTokenUses = $gt_cache->loadData();
-
- $guestToken = null;
- if($forceNew || $guestTokenUses === null || !is_array($guestTokenUses) || count($guestTokenUses) != 2
- || $guestTokenUses[0] <= 0 || (time() - $refresh) > self::GUEST_TOKEN_EXPIRY) {
- $guestToken = $this->getGuestToken($apiKey);
- if ($guestToken === null) {
- if($guestTokenUses === null) {
- returnServerError('Could not parse guest token');
- } else {
- $guestToken = $guestTokenUses[1];
- }
- } else {
- $gt_cache->saveData(array(self::GUEST_TOKEN_USES, $guestToken));
- $r_cache->saveData(time());
- }
- } else {
- $guestTokenUses[0] -= 1;
- $gt_cache->saveData($guestTokenUses);
- $guestToken = $guestTokenUses[1];
- }
-
- $this->apiKey = $apiKey;
- $this->guestToken = $guestToken;
- $this->authHeaders = array(
- 'authorization: Bearer ' . $apiKey,
- 'x-guest-token: ' . $guestToken,
- );
-
- return array($apiKey, $guestToken);
- }
-
- // Get a guest token. This is different to an API key,
- // and it seems to change more regularly than the API key.
- private function getGuestToken($apiKey) {
- $headers = array(
- 'authorization: Bearer ' . $apiKey,
- );
- $opts = array(
- CURLOPT_POST => 1,
- );
-
- try {
- $pageContent = getContents('https://api.twitter.com/1.1/guest/activate.json', $headers, $opts, true);
- $guestToken = json_decode($pageContent['content'])->guest_token;
- } catch (Exception $e) {
- $guestToken = null;
- }
- return $guestToken;
- }
-
- /**
- * Tries to make an API call to twitter.
- * @param $api string API entry point
- * @param $params array additional URI parmaeters
- * @return object json data
- */
- private function makeApiCall($api, $params) {
- $uri = self::API_URI . $api . '?' . http_build_query($params);
-
- $retries = 1;
- $retry = 0;
- do {
- $retry = 0;
-
- try {
- $result = getContents($uri, $this->authHeaders, array(), true);
- } catch (HttpException $e) {
- switch ($e->getCode()) {
- case 401:
- // fall-through
- case 403:
- if ($retries) {
- $retries--;
- $retry = 1;
- $this->getApiKey(1);
- continue 2;
- }
- // fall-through
- default:
- $code = $e->getCode();
- $data = $e->getMessage();
- returnServerError(<<<EOD
+ // put out
+ $this->items[] = $item;
+ }
+
+ usort($this->items, ['TwitterBridge', 'compareTweetId']);
+ }
+
+ private static function compareTweetId($tweet1, $tweet2)
+ {
+ return (intval($tweet1['id']) < intval($tweet2['id']) ? 1 : -1);
+ }
+
+ //The aim of this function is to get an API key and a guest token
+ //This function takes 2 requests, and therefore is cached
+ private function getApiKey($forceNew = 0)
+ {
+ $cacheFac = new CacheFactory();
+
+ $r_cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
+ $r_cache->setScope(get_called_class());
+ $r_cache->setKey(['refresh']);
+ $data = $r_cache->loadData();
+
+ $refresh = null;
+ if ($data === null) {
+ $refresh = time();
+ $r_cache->saveData($refresh);
+ } else {
+ $refresh = $data;
+ }
+
+ $cacheFac = new CacheFactory();
+
+ $cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
+ $cache->setScope(get_called_class());
+ $cache->setKey(['api_key']);
+ $data = $cache->loadData();
+
+ $apiKey = null;
+ if ($forceNew || $data === null || (time() - $refresh) > self::GUEST_TOKEN_EXPIRY) {
+ $twitterPage = getContents('https://twitter.com');
+
+ $jsLink = false;
+ $jsMainRegexArray = [
+ '/(https:\/\/abs\.twimg\.com\/responsive-web\/web\/main\.[^\.]+\.js)/m',
+ '/(https:\/\/abs\.twimg\.com\/responsive-web\/web_legacy\/main\.[^\.]+\.js)/m',
+ '/(https:\/\/abs\.twimg\.com\/responsive-web\/client-web\/main\.[^\.]+\.js)/m',
+ '/(https:\/\/abs\.twimg\.com\/responsive-web\/client-web-legacy\/main\.[^\.]+\.js)/m',
+ ];
+ foreach ($jsMainRegexArray as $jsMainRegex) {
+ if (preg_match_all($jsMainRegex, $twitterPage, $jsMainMatches, PREG_SET_ORDER, 0)) {
+ $jsLink = $jsMainMatches[0][0];
+ break;
+ }
+ }
+ if (!$jsLink) {
+ returnServerError('Could not locate main.js link');
+ }
+
+ $jsContent = getContents($jsLink);
+ $apiKeyRegex = '/([a-zA-Z0-9]{59}%[a-zA-Z0-9]{44})/m';
+ preg_match_all($apiKeyRegex, $jsContent, $apiKeyMatches, PREG_SET_ORDER, 0);
+ $apiKey = $apiKeyMatches[0][0];
+ $cache->saveData($apiKey);
+ } else {
+ $apiKey = $data;
+ }
+
+ $cacheFac2 = new CacheFactory();
+
+ $gt_cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
+ $gt_cache->setScope(get_called_class());
+ $gt_cache->setKey(['guest_token']);
+ $guestTokenUses = $gt_cache->loadData();
+
+ $guestToken = null;
+ if (
+ $forceNew || $guestTokenUses === null || !is_array($guestTokenUses) || count($guestTokenUses) != 2
+ || $guestTokenUses[0] <= 0 || (time() - $refresh) > self::GUEST_TOKEN_EXPIRY
+ ) {
+ $guestToken = $this->getGuestToken($apiKey);
+ if ($guestToken === null) {
+ if ($guestTokenUses === null) {
+ returnServerError('Could not parse guest token');
+ } else {
+ $guestToken = $guestTokenUses[1];
+ }
+ } else {
+ $gt_cache->saveData([self::GUEST_TOKEN_USES, $guestToken]);
+ $r_cache->saveData(time());
+ }
+ } else {
+ $guestTokenUses[0] -= 1;
+ $gt_cache->saveData($guestTokenUses);
+ $guestToken = $guestTokenUses[1];
+ }
+
+ $this->apiKey = $apiKey;
+ $this->guestToken = $guestToken;
+ $this->authHeaders = [
+ 'authorization: Bearer ' . $apiKey,
+ 'x-guest-token: ' . $guestToken,
+ ];
+
+ return [$apiKey, $guestToken];
+ }
+
+ // Get a guest token. This is different to an API key,
+ // and it seems to change more regularly than the API key.
+ private function getGuestToken($apiKey)
+ {
+ $headers = [
+ 'authorization: Bearer ' . $apiKey,
+ ];
+ $opts = [
+ CURLOPT_POST => 1,
+ ];
+
+ try {
+ $pageContent = getContents('https://api.twitter.com/1.1/guest/activate.json', $headers, $opts, true);
+ $guestToken = json_decode($pageContent['content'])->guest_token;
+ } catch (Exception $e) {
+ $guestToken = null;
+ }
+ return $guestToken;
+ }
+
+ /**
+ * Tries to make an API call to twitter.
+ * @param $api string API entry point
+ * @param $params array additional URI parmaeters
+ * @return object json data
+ */
+ private function makeApiCall($api, $params)
+ {
+ $uri = self::API_URI . $api . '?' . http_build_query($params);
+
+ $retries = 1;
+ $retry = 0;
+ do {
+ $retry = 0;
+
+ try {
+ $result = getContents($uri, $this->authHeaders, [], true);
+ } catch (HttpException $e) {
+ switch ($e->getCode()) {
+ case 401:
+ // fall-through
+ case 403:
+ if ($retries) {
+ $retries--;
+ $retry = 1;
+ $this->getApiKey(1);
+ continue 2;
+ }
+ // fall-through
+ default:
+ $code = $e->getCode();
+ $data = $e->getMessage();
+ returnServerError(<<<EOD
Failed to make api call: $api
HTTP Status: $code
Errormessage: $data
EOD
- );
- break;
- }
- }
- } while ($retry);
+ );
+ break;
+ }
+ }
+ } while ($retry);
- $data = json_decode($result['content']);
+ $data = json_decode($result['content']);
- return $data;
- }
+ return $data;
+ }
}