<?php
							 | 
						|
								namespace XRay\Formats;
							 | 
						|
								
							 | 
						|
								use DateTime, DateTimeZone;
							 | 
						|
								use Parse;
							 | 
						|
								
							 | 
						|
								class Twitter {
							 | 
						|
								
							 | 
						|
								  public static function parse($url, $tweet_id, $creds, $json=null) {
							 | 
						|
								
							 | 
						|
								    $host = parse_url($url, PHP_URL_HOST);
							 | 
						|
								    if($host == 'twtr.io') {
							 | 
						|
								      $tweet_id = self::b60to10($tweet_id);
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    if($json) {
							 | 
						|
								      if(is_string($json))
							 | 
						|
								        $tweet = json_decode($json);
							 | 
						|
								      else
							 | 
						|
								        $tweet = $json;
							 | 
						|
								    } else {
							 | 
						|
								      $twitter = new \Twitter($creds['twitter_api_key'], $creds['twitter_api_secret'], $creds['twitter_access_token'], $creds['twitter_access_token_secret']);
							 | 
						|
								      try { 
							 | 
						|
								        $tweet = $twitter->request('statuses/show/'.$tweet_id, 'GET', ['tweet_mode'=>'extended']);
							 | 
						|
								      } catch(\TwitterException $e) {
							 | 
						|
								        return [false, false];
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    if(!$tweet)
							 | 
						|
								      return [false, false];
							 | 
						|
								
							 | 
						|
								    $entry = array(
							 | 
						|
								      'type' => 'entry',
							 | 
						|
								      'url' => $url,
							 | 
						|
								      'author' => [
							 | 
						|
								        'type' => 'card',
							 | 
						|
								        'name' => null,
							 | 
						|
								        'nickname' => null,
							 | 
						|
								        'photo' => null,
							 | 
						|
								        'url' => null
							 | 
						|
								      ]
							 | 
						|
								    );
							 | 
						|
								    $refs = [];
							 | 
						|
								
							 | 
						|
								    // Only use the "display" segment of the text
							 | 
						|
								    $text = mb_substr($tweet->full_text, 
							 | 
						|
								      $tweet->display_text_range[0], 
							 | 
						|
								      $tweet->display_text_range[1]-$tweet->display_text_range[0],
							 | 
						|
								      'UTF-8');
							 | 
						|
								
							 | 
						|
								    if(property_exists($tweet, 'retweeted_status')) {
							 | 
						|
								      // No content for retweets
							 | 
						|
								
							 | 
						|
								      $reposted = $tweet->retweeted_status;
							 | 
						|
								      $repostOf = 'https://twitter.com/' . $reposted->user->screen_name . '/status/' . $reposted->id_str;
							 | 
						|
								      $entry['repost-of'] = $repostOf;
							 | 
						|
								
							 | 
						|
								      list($repostedEntry) = self::parse($repostOf, $reposted->id_str, null, $reposted);
							 | 
						|
								      if(isset($repostedEntry['refs'])) {
							 | 
						|
								        foreach($repostedEntry['refs'] as $k=>$v) {
							 | 
						|
								          $refs[$k] = $v;
							 | 
						|
								        }
							 | 
						|
								      }
							 | 
						|
								
							 | 
						|
								      $refs[$repostOf] = $repostedEntry['data'];
							 | 
						|
								
							 | 
						|
								    } else {
							 | 
						|
								      // Twitter escapes & as & in the text
							 | 
						|
								      $text = html_entity_decode($text);
							 | 
						|
								
							 | 
						|
								      $text = self::expandTweetURLs($text, $tweet);
							 | 
						|
								
							 | 
						|
								      $entry['content'] = ['text' => $text];
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // Published date
							 | 
						|
								    $published = new DateTime($tweet->created_at);
							 | 
						|
								    if(property_exists($tweet->user, 'utc_offset')) {
							 | 
						|
								      $tz = new DateTimeZone(sprintf('%+d', $tweet->user->utc_offset / 3600));
							 | 
						|
								      $published->setTimeZone($tz);
							 | 
						|
								    }
							 | 
						|
								    $entry['published'] = $published->format('c');
							 | 
						|
								
							 | 
						|
								    // Hashtags
							 | 
						|
								    if(property_exists($tweet, 'entities') && property_exists($tweet->entities, 'hashtags')) {
							 | 
						|
								      if(count($tweet->entities->hashtags)) {
							 | 
						|
								        $entry['category'] = [];
							 | 
						|
								        foreach($tweet->entities->hashtags as $hashtag) {
							 | 
						|
								          $entry['category'][] = $hashtag->text;
							 | 
						|
								        }
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // Don't include the RT'd photo or video in the main object. 
							 | 
						|
								    // They get included in the reposted object instead.
							 | 
						|
								    if(!property_exists($tweet, 'retweeted_status')) {
							 | 
						|
								      // Photos and Videos
							 | 
						|
								      if(property_exists($tweet, 'extended_entities') && property_exists($tweet->extended_entities, 'media')) {
							 | 
						|
								        foreach($tweet->extended_entities->media as $media) {
							 | 
						|
								          if($media->type == 'photo') {
							 | 
						|
								            if(!array_key_exists('photo', $entry))
							 | 
						|
								              $entry['photo'] = [];
							 | 
						|
								
							 | 
						|
								            $entry['photo'][] = $media->media_url_https;
							 | 
						|
								
							 | 
						|
								          } elseif($media->type == 'video') {
							 | 
						|
								            if(!array_key_exists('video', $entry))
							 | 
						|
								              $entry['video'] = [];
							 | 
						|
								
							 | 
						|
								            // Find the highest bitrate video that is mp4
							 | 
						|
								            $videos = $media->video_info->variants;
							 | 
						|
								            $videos = array_filter($videos, function($v) {
							 | 
						|
								              return property_exists($v, 'bitrate') && $v->content_type == 'video/mp4';
							 | 
						|
								            });
							 | 
						|
								            if(count($videos)) {
							 | 
						|
								              usort($videos, function($a,$b) {
							 | 
						|
								                return $a->bitrate < $b->bitrate;
							 | 
						|
								              });
							 | 
						|
								              $entry['video'][] = $videos[0]->url;
							 | 
						|
								            }
							 | 
						|
								          }
							 | 
						|
								        }
							 | 
						|
								      }
							 | 
						|
								
							 | 
						|
								      // Place
							 | 
						|
								      if(property_exists($tweet, 'place') && $tweet->place) {
							 | 
						|
								        $place = $tweet->place;
							 | 
						|
								        if($place->place_type == 'city') {
							 | 
						|
								          $entry['location'] = $place->url;
							 | 
						|
								          $refs[$place->url] = [
							 | 
						|
								            'type' => 'adr',
							 | 
						|
								            'name' => $place->full_name,
							 | 
						|
								            'locality' => $place->name,
							 | 
						|
								            'country-name' => $place->country,
							 | 
						|
								          ];
							 | 
						|
								        }
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // Quoted Status
							 | 
						|
								    if(property_exists($tweet, 'quoted_status')) {
							 | 
						|
								      $quoteOf = 'https://twitter.com/' . $tweet->quoted_status->user->screen_name . '/status/' . $tweet->quoted_status_id_str;
							 | 
						|
								      list($quoted) = self::parse($quoteOf, $tweet->quoted_status_id_str, null, $tweet->quoted_status);
							 | 
						|
								      if(isset($quoted['refs'])) {
							 | 
						|
								        foreach($quoted['refs'] as $k=>$v) {
							 | 
						|
								          $refs[$k] = $v;
							 | 
						|
								        }
							 | 
						|
								      }
							 | 
						|
								      $refs[$quoteOf] = $quoted['data'];
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    if($author = self::_buildHCardFromTwitterProfile($tweet->user)) {
							 | 
						|
								      $entry['author'] = $author;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    $response = [
							 | 
						|
								      'data' => $entry
							 | 
						|
								    ];
							 | 
						|
								
							 | 
						|
								    if(count($refs)) {
							 | 
						|
								      $response['refs'] = $refs;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return [$response, $tweet];
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  private static function _buildHCardFromTwitterProfile($profile) {
							 | 
						|
								    if(!$profile) return false;
							 | 
						|
								
							 | 
						|
								    $author = [
							 | 
						|
								      'type' => 'card'
							 | 
						|
								    ];
							 | 
						|
								
							 | 
						|
								    $author['nickname'] = $profile->screen_name;
							 | 
						|
								    $author['location'] = $profile->location;
							 | 
						|
								    $author['bio'] = self::expandTwitterObjectURLs($profile->description, $profile, 'description');
							 | 
						|
								
							 | 
						|
								    if($profile->name)
							 | 
						|
								      $author['name'] = $profile->name;
							 | 
						|
								    else
							 | 
						|
								      $author['name'] = $profile->screen_name;
							 | 
						|
								
							 | 
						|
								    if($profile->url) {
							 | 
						|
								      if($profile->entities->url->urls[0]->expanded_url)
							 | 
						|
								        $author['url'] = $profile->entities->url->urls[0]->expanded_url;
							 | 
						|
								      else
							 | 
						|
								        $author['url'] = $profile->entities->url->urls[0]->url;
							 | 
						|
								    }
							 | 
						|
								    else {
							 | 
						|
								      $author['url'] = 'https://twitter.com/' . $profile->screen_name;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    $author['photo'] = $profile->profile_image_url_https;
							 | 
						|
								
							 | 
						|
								    return $author;
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  private static function expandTweetURLs($text, $object) {
							 | 
						|
								    if(property_exists($object, 'entities') && property_exists($object->entities, 'urls')) {
							 | 
						|
								      foreach($object->entities->urls as $url) {
							 | 
						|
								        $text = str_replace($url->url, $url->expanded_url, $text);
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								    return $text;
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  private static function expandTwitterObjectURLs($text, $object, $key) {
							 | 
						|
								    if(property_exists($object, 'entities') 
							 | 
						|
								      && property_exists($object->entities, $key) 
							 | 
						|
								      && property_exists($object->entities->{$key}, 'urls')) {
							 | 
						|
								      foreach($object->entities->{$key}->urls as $url) {
							 | 
						|
								        $text = str_replace($url->url, $url->expanded_url, $text);
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								    return $text;
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  /**
							 | 
						|
								   * Converts base 60 to base 10, with error checking
							 | 
						|
								   * http://tantek.pbworks.com/NewBase60
							 | 
						|
								   * @param string $s
							 | 
						|
								   * @return int
							 | 
						|
								   */
							 | 
						|
								  function b60to10($s)
							 | 
						|
								  {
							 | 
						|
								    $n = 0;
							 | 
						|
								    for($i = 0; $i < strlen($s); $i++) // iterate from first to last char of $s
							 | 
						|
								    {
							 | 
						|
								      $c = ord($s[$i]); //  put current ASCII of char into $c  
							 | 
						|
								      if ($c>=48 && $c<=57) { $c=bcsub($c,48); }
							 | 
						|
								      else if ($c>=65 && $c<=72) { $c=bcsub($c,55); }
							 | 
						|
								      else if ($c==73 || $c==108) { $c=1; } // typo capital I, lowercase l to 1
							 | 
						|
								      else if ($c>=74 && $c<=78) { $c=bcsub($c,56); }
							 | 
						|
								      else if ($c==79) { $c=0; } // error correct typo capital O to 0
							 | 
						|
								      else if ($c>=80 && $c<=90) { $c=bcsub($c,57); }
							 | 
						|
								      else if ($c==95) { $c=34; } // underscore
							 | 
						|
								      else if ($c>=97 && $c<=107) { $c=bcsub($c,62); }
							 | 
						|
								      else if ($c>=109 && $c<=122) { $c=bcsub($c,63); }
							 | 
						|
								      else { $c = 0; } // treat all other noise as 0
							 | 
						|
								      $n = bcadd(bcmul(60, $n), $c);
							 | 
						|
								    }
							 | 
						|
								    return $n;
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								}
							 |